3. 浏览器环境概览

浏览器环境概览 学习卡片

浏览器指纹采集的第一步,不是立刻去写复杂的 Canvas 或 WebGL 探针,而是先把浏览器主动暴露出来的“基础环境信息”摸清楚。这些信息包括 User-Agent、屏幕分辨率、时区、语言、插件列表等,它们获取成本极低、稳定性差异极大,是后续所有指纹工程的地基。本节将逐一拆解这些常见维度的原理、采集方式、稳定性特征与典型误区。

一、核心原理:为什么这些维度能构成指纹

浏览器为了兼容性和用户体验,会主动向 JavaScript 暴露大量环境属性。这些属性原本是给网页做适配用的,例如根据屏幕宽度决定布局、根据语言加载对应文案、根据时区格式化时间。但当它们被组合起来时,就形成了一个具备一定区分度的特征集合。

需要明确一个关键认知:单个维度的区分度往往很低,组合起来才有价值。例如“中文 + 中国时区”可能覆盖数亿用户,但“中文 + 中国时区 + 1920×1080 + 特定插件组合 + 特定 UA 版本”就能把范围缩小到很小。指纹采集的本质是维度叠加与熵增,而不是依赖某一个“万能字段”。

指纹熵(Entropy)是衡量维度区分能力的核心指标。一个维度取值越分散、分布越均匀,熵越高,区分能力越强。User-Agent 的熵通常较高,但极易被伪造;屏幕分辨率的熵中等,但相对稳定;时区和语言的熵较低,却非常稳定。工程上要做的,是在“稳定性”和“区分度”之间找平衡。

二、常见指纹维度逐项拆解

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 设置

三、方法流程:基础环境指纹采集的标准步骤

  1. 定义采集清单:明确要采集哪些字段,每个字段的用途和预期稳定性。
  2. 封装采集函数:对每个字段做容错处理,避免某个接口不存在导致整体失败。
  3. 归一化处理:统一大小写、去除版本号中的噪声、处理空值。
  4. 计算稳定性权重:根据历史数据给每个维度分配权重。
  5. 组合生成指纹:将归一化后的字段拼接或哈希,形成基础指纹。
  6. 持续验证:定期回采,观察哪些维度发生漂移。
建议把采集结果同时保留“原始值”和“归一化值”。原始值用于排查问题,归一化值用于生成指纹。这样在维度漂移时能快速定位是采集问题还是环境真实变化。

四、维度稳定性对比清单

维度 获取方式 区分度 稳定性 可伪造性
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 会导致误判。
  • 误区二:时区能直接判断地理位置。时区只反映系统设置,用户可手动修改,不能等同于真实位置。
  • 误区三:插件列表是强指纹。现代浏览器已限制插件接口,其区分度大幅下降,不应作为核心维度。
  • 误区四:采集字段越多越好。字段越多,漂移风险越大。应优先选择高稳定性维度,控制指纹的“噪声水平”。