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

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

发布时间:2021 年 5 月 19 日

User-Agent 字符串是浏览器中一个重要的被动指纹采集途径,而且很难处理。不过,收集和处理用户代理数据有各种各样的正当理由,因此需要找到更好的解决方案。 用户代理客户端提示提供了一种明确的方法来声明您对用户代理数据的需求,并提供了以易于使用的格式返回数据的方法。

在本文中,我们将引导您审核对用户代理数据的访问权限,并将用户代理字符串的使用迁移到用户代理客户端提示。

Browser Support

  • Chrome: 90.
  • Edge: 90.
  • Firefox: not supported.
  • Safari: not supported.

Source

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

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

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

从功能角度来看,请考虑代码中记录或处理以下内容的任何位置:

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

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

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

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

  • Sec-CH-UA:浏览器名称和主要/重要版本
  • Sec-CH-UA-Mobile:表示移动设备的布尔值
  • Sec-CH-UA-Platform:操作系统名称

建议的缩减版用户代理字符串也将以向后兼容的方式保留这些基本信息。例如,字符串将包含 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 是一个具有 brand 和 version 属性的对象数组,浏览器可以在其中列出其与这些品牌的兼容性。您可以直接将其作为数组进行访问,也可以使用 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.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 retrofill 是一个小型库,可让您使用根据请求的 navigator.userAgentData 值构建的新字符串覆盖 navigator.userAgent。

例如,以下代码会生成一个额外包含“model”提示的用户代理字符串:

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

生成的字符串会显示 Pixel 5 模型,但仍会显示缩减后的 92.0.0.0,因为未请求 uaFullVersion 提示:

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 代码库中发起讨论,我们可以一起探讨您的问题。