3. 浏览器环境概览
浏览器指纹采集的第一步,不是立刻去写复杂的 Canvas 或 WebGL 探针,而是先把浏览器主动暴露出来的“基础环境信息”摸清楚。这些信息包括 User-Agent、屏幕分辨率、时区、语言、插件列表等,它们获取成本极低、稳定性差异极大,是后续所有指纹工程的地基。本节将逐一拆解这些常见维度的原理、采集方式、稳定性特征与典型误区。
一、核心原理:为什么这些维度能构成指纹
浏览器为了兼容性和用户体验,会主动向 JavaScript 暴露大量环境属性。这些属性原本是给网页做适配用的,例如根据屏幕宽度决定布局、根据语言加载对应文案、根据时区格式化时间。但当它们被组合起来时,就形成了一个具备一定区分度的特征集合。
需要明确一个关键认知:单个维度的区分度往往很低,组合起来才有价值。例如“中文 + 中国时区”可能覆盖数亿用户,但“中文 + 中国时区 + 1920×1080 + 特定插件组合 + 特定 UA 版本”就能把范围缩小到很小。指纹采集的本质是维度叠加与熵增,而不是依赖某一个“万能字段”。
二、常见指纹维度逐项拆解
1. User-Agent
User-Agent 是最经典的指纹字段,通过 navigator.userAgent 获取。它包含浏览器名称、版本、渲染引擎、操作系统等信息。但它的最大问题是可被任意篡改,且现代浏览器正在逐步冻结和简化 UA 字符串。因此,UA 更适合作为辅助校验,而不是唯一依据。
// 采集 User-Agent 及关联字段
const ua = navigator.userAgent;
const uaData = navigator.userAgentData; // 新版浏览器结构化数据
console.log('UA:', ua);
console.log('平台:', navigator.platform);
console.log('厂商:', navigator.vendor);
2. 屏幕分辨率与视口
屏幕相关维度包括物理分辨率、可用分辨率、视口尺寸、设备像素比(DPR)等。它们通过 screen 对象和 window 获取。分辨率稳定性较好,但会受到浏览器缩放、多显示器、窗口大小变化的影响,采集时需要区分“屏幕级”和“窗口级”数据。
const screenInfo = {
width: screen.width,
height: screen.height,
availWidth: screen.availWidth,
availHeight: screen.availHeight,
colorDepth: screen.colorDepth,
pixelDepth: screen.pixelDepth,
devicePixelRatio: window.devicePixelRatio,
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
};
3. 时区与语言
时区通过 Intl.DateTimeFormat().resolvedOptions().timeZone 获取,比传统的 getTimezoneOffset() 更精确。语言通过 navigator.language 和 navigator.languages 获取。这两个维度稳定性极高,用户很少主动修改,因此是很好的“锚点维度”。
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
const offset = new Date().getTimezoneOffset();
const lang = navigator.language;
const langs = navigator.languages;
console.log(tz, offset, lang, langs);
4. 插件与 MIME 类型
传统上通过 navigator.plugins 和 navigator.mimeTypes 采集。但现代浏览器出于隐私考虑,已大幅限制这些接口,返回值趋于统一。因此插件维度的区分度正在快速下降,采集时应记录其长度和名称列表,但不宜作为核心判据。
5. 其他基础维度
navigator.hardwareConcurrency:CPU 逻辑核心数navigator.deviceMemory:设备内存(近似值)navigator.maxTouchPoints:触摸点数量navigator.cookieEnabled:Cookie 是否可用navigator.doNotTrack:DNT 设置
三、方法流程:基础环境指纹采集的标准步骤
- 定义采集清单:明确要采集哪些字段,每个字段的用途和预期稳定性。
- 封装采集函数:对每个字段做容错处理,避免某个接口不存在导致整体失败。
- 归一化处理:统一大小写、去除版本号中的噪声、处理空值。
- 计算稳定性权重:根据历史数据给每个维度分配权重。
- 组合生成指纹:将归一化后的字段拼接或哈希,形成基础指纹。
- 持续验证:定期回采,观察哪些维度发生漂移。
四、维度稳定性对比清单
| 维度 | 获取方式 | 区分度 | 稳定性 | 可伪造性 |
|---|---|---|---|---|
| User-Agent | navigator.userAgent | 高 | 低 | 极易 |
| 屏幕分辨率 | screen.width/height | 中 | 中 | 较易 |
| 设备像素比 | window.devicePixelRatio | 中 | 中 | 较易 |
| 时区 | Intl API | 低 | 高 | 较难 |
| 语言 | navigator.language | 低 | 高 | 较难 |
| 插件列表 | navigator.plugins | 低 | 中 | 中等 |
| CPU 核心数 | hardwareConcurrency | 低 | 高 | 较难 |
五、案例场景:一次分辨率漂移的复盘
某项目在采集用户指纹时,发现同一用户的屏幕分辨率在短时间内从 1920×1080 变为 1536×864。排查后发现,用户并未更换设备,而是把浏览器缩放从 100% 调整到了 125%。由于代码直接读取了 window.innerWidth,导致视口尺寸变化,进而影响了指纹稳定性。
复盘结论:屏幕级维度(screen)和窗口级维度(innerWidth)必须分开存储。屏幕级数据相对稳定,窗口级数据易受缩放和窗口调整影响。生成指纹时应优先使用屏幕级数据,窗口级数据仅作为辅助或直接排除。
六、常见误区与纠正
- 误区一:UA 越详细越好。实际上 UA 越详细越容易暴露异常,且现代浏览器正在统一 UA,过度依赖 UA 会导致误判。
- 误区二:时区能直接判断地理位置。时区只反映系统设置,用户可手动修改,不能等同于真实位置。
- 误区三:插件列表是强指纹。现代浏览器已限制插件接口,其区分度大幅下降,不应作为核心维度。
- 误区四:采集字段越多越好。字段越多,漂移风险越大。应优先选择高稳定性维度,控制指纹的“噪声水平”。