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

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

用户代理字符串是浏览器中的重要被动数字“指纹”收集面,但难以处理。不过,收集和处理用户代理数据有各种正当理由,因此我们需要找到更好的解决方案。用户代理客户端提示既提供了明确声明需要用户代理数据的方法,又提供了以简单易用的格式返回数据的方法。

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

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

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

如果您不知道是否正在使用或在何处使用用户代理数据,请考虑在前端代码中搜索是否使用 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

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

对于第一次加载页面时确实需要额外提示的情况,客户端提示可靠性提案将列出一条路由,用于在连接级设置中指定提示。这会利用 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 repo 中发起讨论,我们可以一起探索您的问题。

摄影:Ricardo Rocha,拍摄自 Unsplash 网站