浏览器指纹-探究前端如何识别用户设备

什么是浏览器指纹?

浏览器指纹,是用来唯一标识你浏览器的一组"特征值"。它不是我们理解中的那种真实指纹,而是通过收集浏览器、操作系统、设备分辨率、字体、插件等信息,组合成的一个独特 ID。

和传统的 Cookie 不同,浏览器指纹不需要在用户设备上存储任何东西,完全是"读取现有信息"来识别用户。

使用背景

在最近的项目中,有个小需求:想用用户的设备作为唯一凭证,来验证身份

一开始我想着简单粗暴点,用 JS 获取手机的 IMEI 或 PC 的序列号。但查了下资料后才发现,这根本行不通------JS 根本没权限访问这些底层硬件信息,安全机制早就把这条路堵死了。

后来才反应过来,我真正想要的,是一个"设备唯一标识",也就是------浏览器指纹。

可行方案

查阅了一些资料之后,目前比较常见的几种浏览器指纹方案如下:

  • Navigator 指纹:浏览器类型、版本、系统平台等信息。
  • Canvas 指纹:让浏览器绘制一段隐藏的图像,然后读取图像的像素差异,不同设备会有微小区别。
  • WebGL 指纹:利用显卡和图形驱动渲染差异,获取设备的唯一特征。
  • 字体、插件、时区、屏幕分辨率等:这些信息组合起来也能提供一定的识别度。

当然,单一方案识别率可能不高,但多种信息结合后,指纹的唯一性就会明显提升。

Navigator 是前端获取浏览器和部分设备环境信息的重要接口。

下面是一些常用的属性和方法(跨浏览器兼容性较好的为主):

属性/方法 作用说明 示例代码
navigator.userAgent 获取浏览器的用户代理字符串,可以用于判断浏览器类型、系统类型 navigator.userAgent
navigator.platform 获取运行环境的操作系统平台类型(如 Win32、Linux x86_64、MacIntel) navigator.platform
navigator.appVersion 获取浏览器版本信息和部分平台信息 navigator.appVersion
navigator.appName 获取浏览器名称(大多数现代浏览器返回 "Netscape") navigator.appName
navigator.language 返回当前浏览器的首选语言(如 "zh-CN"、"en-US") navigator.language
navigator.languages 返回用户的首选语言列表 navigator.languages
navigator.hardwareConcurrency 返回可用的逻辑处理器数量(CPU核心数) navigator.hardwareConcurrency
navigator.plugins 返回当前安装的插件列表(仅桌面浏览器有意义,且有兼容性限制) navigator.plugins
navigator.onLine 判断当前浏览器是否联网 navigator.onLine
navigator.cookieEnabled 判断浏览器是否启用 Cookie navigator.cookieEnabled
navigator.geolocation 提供地理位置定位服务(需要用户授权) navigator.geolocation.getCurrentPosition(...)
navigator.maxTouchPoints 支持的最大触控点个数(触屏设备可用) navigator.maxTouchPoints
navigator.mediaDevices 访问音视频设备管理 API(如获取麦克风、摄像头) navigator.mediaDevices.getUserMedia(...)
navigator.clipboard 读写系统剪贴板(部分浏览器需要 https 环境和权限) navigator.clipboard.writeText("Hello")
navigator.connection 获取网络连接信息对象(如带宽、类型,部分浏览器支持) navigator.connection.effectiveType
navigator.userAgentData 在新标准中可用的一种用户代理信息对象,部分浏览器已支持,用户隐私性更高 navigator.userAgentData

偷个懒,让Tare直接帮我写个Navigator 指纹示例吧。

html 复制代码
<!DOCTYPE html>
<html>

<head>
    <title>Navigator 指纹示例</title>
</head>

<body>
    <h2>Navigator 指纹示例</h2>
    <pre id="output"></pre>
    <script>
        async function getNavigatorFingerprint() {
            // 收集 navigator 相关信息
            const data = {
                userAgent: navigator.userAgent,
                platform: navigator.platform,
                language: navigator.language,
                languages: navigator.languages,
                cookieEnabled: navigator.cookieEnabled,
                hardwareConcurrency: navigator.hardwareConcurrency || 'N/A',
                deviceMemory: navigator.deviceMemory || 'N/A',
                webdriver: navigator.webdriver || false,
            };
            // 将数据转成字符串
            const dataString = JSON.stringify(data);
            // 计算 SHA-256 哈希
            const hashBuffer = await crypto.subtle.digest(
                "SHA-256",
                new TextEncoder().encode(dataString)
            );
            // 转成十六进制字符串
            const hashArray = Array.from(new Uint8Array(hashBuffer));
            const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
            return { data, fingerprint: hashHex };
        }
        getNavigatorFingerprint().then(result => {
            const output = document.getElementById('output');
            output.textContent =
                "采集到的 Navigator 信息:\n" + JSON.stringify(result.data, null, 2) +
                "\n\n生成的指纹(SHA-256):\n" + result.fingerprint;
        });
    </script>
</body>

</html>

代码生成完毕,点击应用直接预览:

经过测试,在同一个电脑上,这个指纹是稳定的,多次执行,这个值不会变。

但这这个指纹明显有缺陷,我系统语言或者浏览器升级后,这个指纹肯定会改变。

Canvas 指纹

由于不同设备(包括操作系统、显卡、驱动、字体渲染引擎等)在绘制同一段 Canvas 内容时会存在细微差异,最终得到的图像数据(通常是像素或转成 base64)在不同设备上往往是不同的。

这些细微差异生成的哈希值就是"指纹",由于只与设备性能有关,指纹稳定性显然比Navigator 指纹高一些。

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>简单Canvas指纹示例</title>
</head>
<body>
    <h2>简单Canvas指纹示例</h2>
    <p>请打开控制台(F12)查看结果</p>

    <script>
        // 创建一个简单的Canvas指纹生成函数
        function generateCanvasFingerprint() {
            // 创建canvas元素
            const canvas = document.createElement('canvas');
            canvas.width = 200;
            canvas.height = 100;
            
            // 获取绘图上下文
            const ctx = canvas.getContext('2d');
            
            // 填充背景
            ctx.fillStyle = 'white';
            ctx.fillRect(0, 0, canvas.width, canvas.height);
            
            // 绘制一些图形和文字
            // 绘制红色矩形
            ctx.fillStyle = 'red';
            ctx.fillRect(20, 20, 50, 50);
            
            // 绘制蓝色圆形
            ctx.fillStyle = 'blue';
            ctx.beginPath();
            ctx.arc(120, 45, 25, 0, Math.PI * 2);
            ctx.fill();
            
            // 绘制文本
            ctx.fillStyle = 'black';
            ctx.font = '16px Arial';
            ctx.fillText('Canvas指纹', 60, 80);
            
            // 获取canvas数据URL
            const dataURL = canvas.toDataURL();
            
            // 简单哈希函数
            function simpleHash(str) {
                let hash = 0;
                for (let i = 0; i < str.length; i++) {
                    const char = str.charCodeAt(i);
                    hash = ((hash << 5) - hash) + char;
                    hash = hash & hash; // 转换为32位整数
                }
                return hash.toString(16); // 转换为16进制
            }
            
            // 计算指纹
            const fingerprint = simpleHash(dataURL);
            
            return {
                fingerprint: fingerprint,
                dataURL: dataURL
            };
        }
        
        // 生成并输出指纹
        const result = generateCanvasFingerprint();
        console.log('Canvas指纹:', result.fingerprint);
        console.log('Canvas数据URL前100个字符:', result.dataURL.substring(0, 100) + '...');
        
        // 如果浏览器支持更安全的哈希算法,也可以使用它
        if (window.crypto && window.crypto.subtle) {
            const encoder = new TextEncoder();
            const data = encoder.encode(result.dataURL);
            
            window.crypto.subtle.digest('SHA-256', data)
                .then(hashBuffer => {
                    // 将哈希缓冲区转换为十六进制字符串
                    const hashArray = Array.from(new Uint8Array(hashBuffer));
                    const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
                    
                    console.log('Canvas指纹(SHA-256):', hashHex);
                });
        }
    </script>
</body>
</html>

生成的指纹还是很不错的。

其他几种方式生成浏览器指纹都大同小异,这里就不介绍了。

相关推荐
Goboy3 小时前
Trae 与颜色板生成器,为前端开发提供智能配色解决方案
ai编程·trae
Goboy3 小时前
Trae 开发文本大小写转换器,结合 MCP Server 自动部署
ai编程·trae
银空飞羽15 小时前
再学学MCP间接提示词注入
安全·mcp·trae
盏灯21 小时前
Trae Agent —— 🥘 世纪难题,今晚吃啥?🍳
ai编程·trae
cpp加油站1 天前
发现宝藏:腾讯EdgeOne Pages & 掘金MCP,Trae内一键部署网页(玩转100个MCP系列第一弹)
ai编程·mcp·trae
cpp加油站1 天前
(保姆级教程)Trae中使用clangd插件实现c++代码函数列表、变量补全、代码跳转等功能
c++·ai编程·trae
康伯巴奇1 天前
关于我用AI10分钟做了一个网站这件事
trae
Goboy1 天前
Trae 智能体实现大海、日出与白云的动态首页背景设计,MCP一键部署开箱即用。
ai编程·trae
Favorite_Ystar1 天前
装箱算法实战指南:从原理调研到代码实现
trae
用户4099322502121 天前
FastAPI权限迷宫:RBAC与多层级依赖的魔法通关秘籍
后端·ai编程·trae