12k
All articles

用户代理字符串还值得信任吗?

了解User-Agent中哪些信息仍可靠、哪些被浏览器固定,以及如何用Chromium Client Hints和功能检测取代脆弱的字符串解析。

OpenReplay Team
OpenReplay Team
用户代理字符串还值得信任吗?

部分可以。用户代理(User Agent,UA)字符串仍能可靠地反映浏览器系列、主版本号、移动端或桌面端以及操作系统系列。但它无法反映操作系统版本、设备型号、CPU 架构,在 Chromium 系浏览器中也无法反映浏览器的次版本号。

如果你在日志里看到某台手机上报了 Android 10; K,而它显然没有运行 Android 10;或者一台 M 系列 MacBook 上报了 Mac OS X 10_15_7,那么并没有什么出了问题。这些值是占位值,是浏览器有意发送的。

本文将拆解一条当前的 Chrome UA 字符串,说明各个浏览器引擎分别冻结了哪些字段。随后介绍 Chromium 中用什么替代这条字符串,以及在哪些情况下解析它仍然是合理的选择。

核心要点

  • 所有主流浏览器的 User-Agent 字符串仍以 Mozilla/5.0 开头。这是一个兼容性标记,无法说明发送它的是哪款浏览器。
  • 无论实际的操作系统版本是什么,Chrome 都会上报 Windows NT 10.0; Win64; x64、Macintosh; Intel Mac OS X 10_15_7、X11; Linux x86_64 或 Linux; Android 10; K。它的次版本号、构建号和补丁号始终为 0.0.0。
  • 从 Safari 26 起,iOS、iPadOS 和 visionOS 上的 Safari 会上报冻结的操作系统版本。Mac 上的 Safari 自 2017 年起就已冻结 macOS 版本。
  • User-Agent Client Hints(用户代理客户端提示)仅存在于 Chromium 系浏览器中。Firefox 和 Safari 不发送任何 Sec-CH-UA-* 请求头。
  • 任何客户端都可以发送任意 User-Agent,因此该请求头只能过滤掉主动表明身份的爬虫。

Chrome 用户代理字符串实际表达了什么?

在当前的 Chrome 桌面版 UA 字符串中,只有主版本号这一段会随版本发布而变化,其余部分要么是固定的兼容性标记,要么是冻结的平台值。以下是 Windows 上 Chrome 154 的 UA 字符串:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36
字段声称的含义是否属实?
Mozilla/5.0兼容 Mozilla 的浏览器没有意义,所有浏览器都会发送
(Windows NT 10.0; Win64; x64)Windows 10,64 位 x86已冻结。Windows 11 和 ARM 设备发送的值完全相同
AppleWebKit/537.36 (KHTML, like Gecko)源自 KHTML、与 Gecko 相似的 WebKit 引擎兼容性遗留。Chrome 的引擎实际是 Blink
Chrome/154.0.0.0Chrome 154.0.0.0主版本号属实,0.0.0 是占位值
Safari/537.36Safari并非 Safari。保留它是为了让嗅探“Safari”的代码仍能匹配

MDN 的 Firefox 参考文档将 Mozilla/5.0 描述为一个声明兼容 Mozilla 的通用标记,几乎所有浏览器无论使用何种引擎都会发送它。MDN 的 User-Agent 参考文档也确认,基于 Blink 的浏览器携带 KHTML, like Gecko 和 Safari 纯粹是作为兼容性标记。在 Chrome 的 UA 字符串中,AppleWebKit/537.36 (KHTML, like Gecko) 和 Safari/537.36 既不描述 Chrome 的引擎,也不代表 Safari。它们是固定标记,保留下来是为了让旧的嗅探代码仍能匹配。如果想查看你自己 UA 字符串的类似拆解,可以将其粘贴到 OpenReplay 用户代理解析器中。

哪些用户代理字段已被冻结,哪些仍然可靠?

三大引擎都已冻结了字符串中的高熵部分,即操作系统版本、设备型号和 CPU 架构。Chromium 还会将浏览器次版本号置零。低熵部分仍能反映真实的浏览器信息。Chrome 的 User-Agent Reduction(UA 精简)计划从 Chrome 101(2022 年)开始将次版本号、构建号和补丁号固定为 0.0.0。允许网站继续获取完整字符串的弃用试用(deprecation trial)已于 2023 年 9 月 23 日结束,此后所有页面加载收到的都是精简后的字符串。MDN 的 UA 精简指南列出了这些固定的平台值,其中包括 Android 上的 Android 10; K。

Chrome / EdgeFirefoxSafari
浏览器系列、主版本号真实真实真实(Version/)
移动端/桌面端、操作系统系列真实真实真实
操作系统版本冻结设有上限(macOS 10.15、Android 10)冻结(macOS;iOS 自 26 起)
设备型号Android 上为 K从不发送从不发送
CPU 架构冻结冻结Mac 始终为“Intel”
次版本号0.0.0不公开真实(Version/)
UA Client Hints支持不支持不支持

Edge 使用与 Chrome 相同的冻结标记,并额外添加 Edg/。从 Firefox 87 起,Firefox 将 Big Sur 及之后的所有 macOS 版本都上报为 10.15,并把 Apple Silicon Mac 标记为 Intel。从 Firefox for Android 122 起,无论实际版本如何,它都上报 Android 10。根据 WebKit 发布的 Safari 26.0 文章,Mac 上的 Safari 自 2017 年起一直发送相同的 Intel Mac OS X 10_15_7。从 Safari 26(2025 年 9 月)开始,iOS、iPadOS 和 visionOS 上的 Safari 也改为上报一个冻结的 iOS 18 版本,而不是真实版本。Safari 26.0 发送的是 18_6。从 Safari 26.2 起,WebKit 将该值固定为 iOS 18 的最后一个版本,因此当前的字符串发送的是 18_7。Version/ 标记仍会随每次发布更新。

Chromium 的 UA 精简覆盖 Windows、macOS、Linux、ChromeOS 和 Android 上的 Chrome,但不包括 Android WebView 和 Chrome for iOS。这在实践中意味着:

  • Windows 10 和 Windows 11 看起来完全相同。
  • 在三大引擎中,Apple Silicon Mac 都会上报为“Intel”。
  • 带有 Android 10; K 的 Chrome for Android 字符串并不代表这是一台 Android 10 设备。所有 Android 上的 Chrome 都会发送这个版本号和占位型号 K。

为什么特性检测优于浏览器检测?

浏览器的名称无法可靠地说明它具备哪些能力。特性检测直接检查能力本身,这一点在 UA 字符串被冻结之前就已成立。基于 UA 的检查会在以下情况下失效:字符串提供了虚假信息,新浏览器实现了该 API,或者旧版本缺少该 API。Baseline 提供了跨浏览器视角,告诉你某个特性何时可以放心使用。如果尚不能放心使用,可以用运行时检查来弥补:

// Brittle: guesses capability from a name
if (/Chrome\/\d+/.test(navigator.userAgent)) enableShareButton();

// Direct: asks the browser
if ('share' in navigator) enableShareButton();

User-Agent Client Hints 是如何工作的?

User-Agent Client Hints 是一组 Sec-CH-UA-* 请求头。Chromium 系浏览器通过发送这些请求头,来提供 UA 字符串中不再携带的详细信息。Chrome 和 Edge 默认发送 Sec-CH-UA、Sec-CH-UA-Mobile 和 Sec-CH-UA-Platform,Firefox 和 Safari 则一个都不发送。MDN 将 Sec-CH-UA-Platform 归为低熵提示,因此即使服务器没有请求,浏览器也会携带它,除非被权限策略(Permissions Policy)阻止。其他所有提示都必须由服务器主动请求。

要请求提示,服务器需要在 Accept-CH 中列出这些提示,浏览器随后会在向该源发出的后续安全请求中携带它们。

Accept-CH 对首个请求不起作用。如果希望在第一个请求中就获得高熵提示,服务器需要在 Accept-CH 之外,同时在 Critical-CH 中声明该提示。这样浏览器不会渲染首次响应,而是携带该提示重新发送请求。服务器还应将该提示加入 Vary,以便缓存分别存储各个版本。

import https from 'node:https';
import { readFileSync } from 'node:fs';

https.createServer(
  { key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },
  (req, res) => {
    const platform = req.headers['sec-ch-ua-platform'];           // default hint
    const version = req.headers['sec-ch-ua-platform-version'];    // opt-in only

    res.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch');
    res.setHeader('Critical-CH', 'Sec-CH-UA-Platform-Version');
    res.setHeader('Vary', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch');
    res.setHeader('Content-Type', 'text/plain');
    res.end(`platform=${platform ?? 'n/a'} version=${version ?? 'n/a'}\n`);
  }
).listen(8443);

Node 会将请求头名称转为小写。请求头的值以带引号的结构化字段(structured field)字符串形式到达,例如 "Windows"。在浏览器端,navigator.userAgentData.getHighEntropyValues() 无需任何请求头配置即可返回相同的数据。MDN 已将 uaFullVersion 标记为弃用,建议改用 fullVersionList。

async function describeClient() {
  const uaData = navigator.userAgentData;
  if (!uaData) return { source: 'ua', ua: navigator.userAgent }; // Firefox, Safari
  const high = await uaData.getHighEntropyValues([
    'platformVersion', 'architecture', 'model', 'fullVersionList',
  ]);
  return { source: 'ua-ch', brands: uaData.brands, mobile: uaData.mobile, ...high };
}

什么情况下解析用户代理字符串仍然合理?

如果你只需要那些仍然可靠的字段,并且判断出错的代价很低,那么解析 UA 字符串仍然是合理的。

分析统计分组。 对于“Chrome 154、桌面端、Windows”这类分析统计分组,UA 解析是准确的。但“Windows 10”或“Android 10”这样的分组并不准确,因为它会悄无声息地把更新的版本也归入其中,所以只应将其视为操作系统系列。先检查 Edg/ 再检查 Chrome/,先检查 Chrome/ 再检查 Safari/,因为每个字符串都包含该顺序中排在其后的标记。

爬虫过滤。 RFC 9110 将 User-Agent 定义为由客户端提供的请求头,没有任何机制对其进行校验,任何客户端都可以发送任意值。该请求头能识别出主动表明身份的爬虫,但对不表明身份的爬虫毫无作用。

技术支持工单。 当用户报告 bug 时,你通常只需要大致了解他们使用的环境,而不需要确切的构建版本。会话回放(session replay)工具会随每个会话记录 UA,这足以判断某个 bug 能在 macOS 上的 Firefox 中复现、而在 Chrome 中无法复现。同时记录特性检测的结果,可以进一步缩小排查范围。

结论

对于浏览器系列、主版本号、移动端或桌面端以及操作系统系列,你可以信任用户代理字符串,其余内容都应视为占位值。基于这些结论,建议采取以下行动:

  • 审查代码,找出所有从 UA 字符串中读取操作系统版本、设备型号或次版本号的分支。
  • 用特性检测替换基于 UA 的能力检查。
  • 对于确实需要平台细节的场景,改用 Client Hints,并为不发送这些提示的 Firefox 和 Safari 提供回退方案。

常见问题

如果用户代理显示的是 Windows NT 10.0,如何区分 Windows 11 和 Windows 10?

请求 platformVersion 客户端提示,可以通过 navigator.userAgentData.getHighEntropyValues(['platformVersion']) 获取,也可以发送 Accept-CH: Sec-CH-UA-Platform-Version。根据 Microsoft 的文档,1.0.0 至 10.0.0 的值对应 Windows 10,13.0.0 及以上对应 Windows 11,其示例代码也将主版本号大于等于 13 视为 Windows 11。Firefox 不发送 Client Hints,因此无法区分这两者。

通过 Accept-CH 请求的提示,浏览器会持续发送多久?

Chrome 会将每个源的 Accept-CH 偏好保存到磁盘,自 Chrome 103 起不再设置固定的过期时间。这些偏好会一直保留,直到用户清除该源的 Cookie 或网站数据,并且也会随会话 Cookie 一同被清除。服务器可以重新发送 Accept-CH 来替换提示列表,发送空的 Accept-CH 来停止所有提示,或者发送 Clear-Site-Data: 'clientHints'。

为什么 Sec-CH-UA 请求头中会包含 Not A;Brand 这样的品牌?

这是一个刻意伪造的条目,称为 GREASE。Chromium 会添加一个故意错误、版本号很低的品牌,并不断变换其标点符号及其在列表中的位置。这迫使服务器正确解析该请求头,而不是匹配固定字符串或固定的品牌列表。应将 Sec-CH-UA 作为结构化字段列表进行解析,查找你需要的品牌(例如 Chromium、Google Chrome 或 Microsoft Edge),并忽略所有无法识别的条目。

为什么我的用户代理解析器会把 iPhone 上的 Chrome 识别为 Safari?

Chrome for iOS 发送的是 Mobile Safari 的用户代理字符串,只是用 CriOS/ 标记替换了 Version/,因此字符串中没有 Chrome/ 标记。Firefox for iOS 的做法相同,使用的是 FxiOS/。只查找 Chrome/ 和 Firefox/ 的解析器最终会落入 Safari 分支,因此应优先检查 CriOS/ 和 FxiOS/。Chromium 的 UA 精简并不覆盖 Chrome for iOS。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.