迁移到用户代理客户端提示

将网站从依赖用户代理字符串迁移到新的用户代理客户端提示的策略。

User-Agent 字符串是浏览器中重要的被动指纹识别途径,而且难以处理。不过,收集和处理用户代理数据有各种各样的合理原因,因此我们需要找到更好的解决方案。用户代理客户端提示既提供了一种明确声明您需要用户代理数据的方式,也提供了以易于使用的方式返回数据的方法。

本文将逐步介绍如何审核您对用户代理数据的访问权限,以及如何将用户代理字符串的使用迁移到用户代理客户端提示。

与任何形式的数据收集一样,您应始终了解自己收集数据的原因。无论您是否要采取任何措施,第一步都是了解您在哪里以及出于何种原因使用用户代理数据。

如果您不知道是否有在使用用户代理数据或在哪里使用,不妨考虑在前端代码中搜索 navigator.userAgent 的使用情况,并在后端代码中搜索 User-Agent HTTP 标头的使用情况。您还应检查前端代码,确保未使用已废弃的功能,例如 navigator.platformnavigator.appVersion

从功能角度来看,请考虑代码中进行记录或处理的任意位置:

  • 浏览器名称或版本
  • 操作系统名称或版本
  • 设备品牌或型号
  • CPU 类型、架构或位数(例如 64 位)

您可能还在使用第三方库或服务来处理用户代理。在这种情况下,请检查相应设备是否正在更新以支持 User-Agent Client Hints。

是否只使用基本的用户代理数据?

默认的用户代理客户端提示集包括:

  • Sec-CH-UA:浏览器名称和主要/重要版本
  • Sec-CH-UA-Mobile:指示移动设备的布尔值
  • Sec-CH-UA-Platform:操作系统名称
    • 请注意,规范已更新,很快就会反映在 Chrome 和其他基于 Chromium 的浏览器中。

我们提议的用户代理字符串的缩减版本也将以向后兼容的方式保留这些基本信息。例如,字符串将包含 Chrome/90.0.0.0,而不是 Chrome/90.0.4430.85

如果您仅检查 user-agent 字符串中的浏览器名称、主要版本或操作系统,则您的代码将继续运行,但您可能会看到废弃警告。

虽然您可以并应迁移到 User-Agent Client Hints,但可能存在旧版代码或资源限制,导致无法迁移。以这种向后兼容的方式减少用户代理字符串中的信息,旨在确保虽然现有代码会收到更少的详细信息,但仍应保留基本功能。

策略:按需的客户端 JavaScript API

如果您目前使用的是 navigator.userAgent,则应先改为优先使用 navigator.userAgentData,然后再回退到解析 user-agent 字符串。

if (navigator.userAgentData) {
  // use new hints
} else {
  // fall back to user-agent string parsing
}

如果您要检查移动设备或桌面设备,请使用布尔值 mobile

const isMobile = navigator.userAgentData.mobile;

userAgentData.brands 是具有 brandversion 属性的对象数组,浏览器能够在其中列出与这些品牌的兼容性。您可以直接将其作为数组访问,也可以使用 some() 调用来检查是否存在特定条目:

function isCompatible(item) {
  // In real life you most likely have more complex rules here
  return ['Chromium', 'Google Chrome', 'NewBrowser'].includes(item.brand);
}
if (navigator.userAgentData.brands.some(isCompatible)) {
  // browser reports as compatible
}

如果您需要更详细的高熵用户代理值,则需要指定该值,并在返回的 Promise 中检查结果:

navigator.userAgentData.getHighEntropyValues(['model'])
  .then(ua => {
    // requested hints available as attributes
    const model = ua.model
  });

如果您想从服务器端处理切换到客户端处理,也可以使用此策略。JavaScript API 不需要访问 HTTP 请求标头,因此可以随时请求 user-agent 值。

策略:静态服务器端标头

如果您在服务器上使用 User-Agent 请求标头,并且您对该数据的需求在整个网站中相对一致,则可以在响应中将所需的客户端提示指定为静态集。这是一种相对简单的方法,因为您通常只需在一个位置对其进行配置。例如,如果您已在 Web 服务器配置中添加了标头、您的托管配置,或者用于您网站的框架或平台的顶级配置,那么它可能位于您的 Web 服务器配置中。

如果您要根据用户代理数据转换或自定义所提供的响应,不妨考虑采用此策略。

浏览器或其他客户端可能会选择提供不同的默认提示,因此最好指定您需要的所有内容,即使这些内容通常是默认提供的也是如此。

例如,Chrome 的当前默认值将表示为:

⬇️ 响应标头

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

如果您还想在响应中接收设备型号,则应发送以下内容:

⬇️ 响应标头

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA

在服务器端处理此请求时,您应先检查是否已发送所需的 Sec-CH-UA 标头,然后在该标头不可用时回退到 User-Agent 标头解析。

策略:将提示委托给跨源请求

如果您请求的跨源或跨网站子资源需要在其请求中发送 User-Agent Client Hints,则需要使用权限政策明确指定所需的提示。

例如,假设 https://blog.sitehttps://cdn.site 上托管资源,https://cdn.site 可以返回针对特定设备优化的资源。https://blog.site 可以请求 Sec-CH-UA-Model 提示,但需要使用 Permissions-Policy 标头明确将其委托给 https://cdn.site客户端提示基础架构草稿中提供了政策控制的提示列表。

⬇️ blog.site 委托提示的响应

Accept-CH: Sec-CH-UA-Model
Permissions-Policy: ch-ua-model=(self "https://cdn.site")

⬆️ 对 cdn.site 上的子资源的请求包含委托的提示

Sec-CH-UA-Model: "Pixel 5"

您可以为多个源(而不仅仅是 ch-ua 范围)指定多个提示:

⬇️ blog.site 将多个提示委托给多个源的响应

Accept-CH: Sec-CH-UA-Model, DPR
Permissions-Policy: ch-ua-model=(self "https://cdn.site"),
                    ch-dpr=(self "https://cdn.site" "https://img.site")

策略:将提示委托给 iframe

跨源 iframe 的运作方式与跨源资源类似,但您需要在 allow 属性中指定要委托的提示。

⬇️ blog.site 的回复

Accept-CH: Sec-CH-UA-Model

↪️ blog.site 的 HTML

<iframe src="https://widget.site" allow="ch-ua-model"></iframe>

⬆️ 发给 widget.site 的请求

Sec-CH-UA-Model: "Pixel 5"

iframe 中的 allow 属性将替换 widget.site 可能自行发送的任何 Accept-CH 标头,因此请确保您已指定 iframe 所用网站所需的所有内容。

策略:动态服务器端提示

如果您在用户体验历程中的特定部分需要比整个网站的其他部分提供更多提示选项,则可以选择按需请求这些提示,而不是在整个网站中静态请求这些提示。这种方式的管理比较复杂,但如果您已按路由设置不同的标头,则可能可行。

这里需要注意的重要一点是,Accept-CH 标头的每个实例都会有效地覆盖现有集合。因此,如果您要动态设置标头,则每个网页都必须请求所需的完整提示集。

例如,您可能希望在网站上的一个部分提供与用户的操作系统匹配的图标和控件。为此,您可能还需要额外提取 Sec-CH-UA-Platform-Version 以提供适当的子资源。

⬇️ /blog 的响应标头

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA

⬇️ /app 的响应标头

Accept-CH: Sec-CH-UA-Mobile, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, Sec-CH-UA

策略:首次请求时需要提供服务器端提示

在某些情况下,您可能需要在首次请求时提供的提示数目超出默认的一组,但这种情况可能很少见,因此请务必检查原因。

第一个请求实际上是指在该浏览会话中发送的针对该来源的第一个顶级请求。默认的一组提示包括浏览器名称(包含主要版本)、平台和移动指示器。因此,这里要问的问题是,您是否需要在初始网页加载时获取扩展数据?

如需针对首个请求获得更多提示,您可以选择以下两种方式。首先,您可以使用 Critical-CH 标头。此属性采用与 Accept-CH 相同的格式,但会告知浏览器,如果首次发送的请求未包含关键提示,则应立即重试该请求。

⬆️ 初始请求

[With default headers]

⬇️ 响应标头

Accept-CH: Sec-CH-UA-Model
Critical-CH: Sec-CH-UA-Model

🔃? 浏览器使用额外标头重试初始请求

[With default headers + …]
Sec-CH-UA-Model: Pixel 5

这会导致在首次请求时产生重试开销,但实现成本相对较低。发送额外的标头,浏览器会完成其余操作。

如果您确实需要在首次加载网页时提供额外的提示,客户端提示可靠性提案提供了一种在连接级设置中指定提示的方法。这会利用 TLS 1.3 的应用层协议设置(ALPS) 扩展,以启用 HTTP/2 和 HTTP/3 连接提前传递提示。这项功能目前仍处于非常早期阶段,但如果您积极管理自己的 TLS 和连接设置,现在是贡献自己的力量的理想时机。

策略:旧版支持

您的网站上可能存在依赖于 navigator.userAgent 的旧版代码或第三方代码,包括将要缩减的用户代理字符串部分。从长远来看,您应计划改用等效的 navigator.userAgentData 调用,但目前有临时解决方案。

UA-CH 回填是一个小型库,可让您使用由请求的 navigator.userAgentData 值构建的新字符串覆盖 navigator.userAgent

例如,以下代码将生成一个用户代理字符串,其中还包含“model”提示:

import { overrideUserAgentUsingClientHints } from './uach-retrofill.js';
overrideUserAgentUsingClientHints(['model'])
  .then(() => { console.log(navigator.userAgent); });

生成的字符串将显示 Pixel 5 模型,但由于未请求 uaFullVersion 提示,因此仍会显示经过缩减的 92.0.0.0

Mozilla/5.0 (Linux; Android 10.0; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.0.0 Mobile Safari/537.36

更多支持

如果这些策略不适用于您的用例,请在 privacy-sandbox-dev-support 代码库中发起讨论,我们可以一起探讨您的问题。

照片由 Ricardo Rocha 拍摄,选自 Unsplash