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

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

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

本文将介绍如何审核您对用户代理数据的访问权限,以及如何将用户代理字符串迁移到 User-Agent Client Hints。

审核用户代理数据的收集和使用

与任何形式的数据收集一样,您应该始终了解收集数据的原因。无论您是否采取任何措施,第一步都是了解您在何处以及为什么使用用户代理数据。

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

从功能角度来看,请考虑代码中您要录制或处理的任何位置:

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

还有一种可能是,您在使用第三方库或服务来处理用户代理。在这种情况下,请检查它们是否会更新以支持用户代理客户端提示。

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

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

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

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

如果您只检查用户代理字符串中的浏览器名称、主要版本或操作系统,那么您的代码将会继续运行,但您可能会看到弃用警告。

虽然您可以并且应该迁移到用户代理客户端提示,但您的旧版代码或资源限制可能会阻止这一点。以这种向后兼容的方式减少用户代理字符串中的信息,是为了确保现有代码接收到的信息较少,但仍应保留基本功能。

策略:按需客户端 JavaScript API

如果您目前使用的是 navigator.userAgent,则应先转换为首选 navigator.userAgentData,然后再回退到解析用户代理字符串。

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 请求标头,并且您对整个网站的需求相对一致,那么您可以在响应中将所需的客户端提示指定为一个静态集。这是一种相对简单的方法,因为您通常只需要在一个位置对其进行配置。例如,如果您已在 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 标头解析。

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

如果您请求的跨源或跨网站子资源需要在请求中发送用户代理客户端提示,则您需要使用权限政策明确指定所需的提示。

例如,假设 https://blog.sitehttps://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

这会产生第一个请求的重试开销,但实现成本相对较低。只需发送额外的标头,浏览器就会完成剩余的工作。

如果您确实需要在首次网页加载时需要其他提示,则 Client Hints 可靠性提案会安排路线以在连接级设置中指定提示。它利用了 TLS 1.3 的应用层协议设置(ALPS) 扩展,以便在 HTTP/2 和 HTTP/3 连接上提前传递提示。这仍处于早期阶段,但如果您主动管理自己的 TLS 和连接设置,那么现在正是贡献力量的理想时机。

策略:旧版支持

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

UA-CH retrofill 是一个小型库,可让您使用根据请求的 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 网站