WWDC Quick Look 💓 By SwiftGGTeam
Replace CAPTCHAs with Private Access Tokens

Replace CAPTCHAs with Private Access Tokens

观看原视频

Highlight

CAPTCHA 是 Web 上最令人厌烦的体验之一。它存在的目的是防欺诈,但代价是伤害正常用户体验、侵犯隐私(通过 IP 追踪)和制造无障碍障碍。


核心内容

CAPTCHA 解决的是服务器的信任问题。

当用户注册账号、登录账号或购买商品时,服务器需要判断请求来自正常用户,还是来自攻击者和 bot。传统做法是弹出 CAPTCHA,让用户完成一次额外挑战。

问题是,这个挑战会打断正常用户。演讲一开始用登录 Financial Times 的例子说明:同一个登录流程,在 iOS 15 上会遇到 CAPTCHA;在支持 Private Access Tokens 的 iOS 16 上,用户可以直接进入网站(04:09)。

CAPTCHA 还有两个更深的问题。很多服务为了判断客户端是否可信,会依赖 IP 地址追踪或指纹识别;这和 Safari、Mail Privacy Protection、iCloud Private Relay 的隐私方向冲突。CAPTCHA 也会阻挡有障碍或语言障碍的真实用户(02:04)。

Private Access Tokens(私有访问令牌)把这件事换成系统级证明。服务器不再要求用户做题,而是让支持 iOS 16 或 macOS Ventura 的客户端自动证明:它来自真实的 Apple 设备、设备已解锁、用户已登录 Apple ID,并且请求来自已签名的 app 或 Safari/WebKit(02:46)。

这个证明不会把用户身份交给网站。Apple 使用 IETF Privacy Pass 工作组正在标准化的协议,并用 RSA Blind Signatures(RSA 盲签名)让令牌不可关联。服务器只能验证令牌有效,不能识别用户,也不能用令牌长期追踪用户(04:44)。


详细内容

协议:服务器发起 PrivateToken 挑战

05:02)Private Access Tokens 从一个 HTTP authentication challenge 开始。服务器返回 PrivateToken 认证方案,并指定自己信任的 token issuer(令牌签发方)。

本 session 没有官方代码片段。下面是根据逐字稿整理的概念性 HTTP 交互,用来说明协议字段的位置;具体格式应以你选择的 issuer 文档为准。

HTTP/1.1 401 Unauthorized
WWW-Authenticate: PrivateToken issuer="https://issuer.example"

关键点:

  • 第 1 行:服务器用 401 响应表示当前请求需要额外认证。
  • 第 2 行:WWW-Authenticate 携带认证挑战。
  • 第 2 行:PrivateToken 是 session 中提到的新 HTTP authentication method。
  • 第 2 行:issuer 表示服务器信任哪个 token issuer 来签发令牌。

这个挑战有一个重要限制:如果用于网站,挑战必须来自 first-party domain(第一方域名),也就是主站 URL 的子域名,不能来自嵌入页面的第三方域名(09:35)。

盲化、设备证明和签发

05:58)客户端需要令牌时,会先联系 iCloud attester(iCloud 证明服务)。客户端发送的是 blinded token request(盲化后的令牌请求),因此 iCloud attester 无法把它和原始服务器挑战关联起来。

接着,iCloud attester 用设备 Secure Enclave(安全隔区)里的证书做 device attestation(设备证明),并检查 Apple ID 状态。它还可以做 rate-limiting(速率限制),判断设备是否符合正常使用模式,或是否可能被用于设备农场。

如果客户端通过检查,attester 把请求交给 issuer。issuer 信任 iCloud attester,因此签发令牌。issuer 不知道客户端是谁。客户端收到签名后执行 unblinding(去盲化),再把令牌交回原始服务器验证。

Client -> Server: HTTP request
Server -> Client: PrivateToken challenge
Client -> iCloud Attester: blinded token request
Attester -> Issuer: validated token request
Issuer -> Client: signed blinded token
Client -> Server: unblinded signed token
Server: validate issuer signature

关键点:

  • 第 1 行:流程从普通 HTTP 请求开始。
  • 第 2 行:服务器决定什么时候需要额外信任信号。
  • 第 3 行:客户端发出的请求已盲化,避免和服务器挑战关联。
  • 第 4 行:attester 通过设备证明后,才把请求交给 issuer。
  • 第 5 行:issuer 只负责签名,不接收用户身份。
  • 第 6 行:客户端把去盲化后的签名令牌交给服务器。
  • 第 7 行:服务器验证签名有效,但不能用令牌识别客户端。

服务器接入的三个步骤

07:19)服务器接入 Private Access Tokens 有三步。

第一,选择 token issuer。issuer 可以是现有 CAPTCHA 或反欺诈服务、Web hosting 服务,或 CDN。演讲提到,在 iOS 16 和 macOS Ventura beta 阶段,Fastly 和 Cloudflare 已经可以测试;其他 CAPTCHA provider、Web hosting 服务和 CDN 后续也可以运行 issuer(07:48)。

第二,在需要验证客户端时发送 PrivateToken challenge。你可以让现有 CAPTCHA 或反欺诈服务把 challenge 集成到脚本里,也可以由自己的服务器直接发送(09:06)。

第三,验证客户端返回的 token。服务器需要用 issuer 的 public key(公钥)检查 token 有效性,并处理 replay attack(重放攻击)。Apple 明确说 token 应该一次性使用,可以通过记录已使用 token,或要求 token 签名 challenge 中的唯一值来防重放(09:50)。

下面是概念性服务器逻辑,展示这三个判断点。

async function handleRequest(request) {
  const token = request.headers.get("Authorization");

  if (!token) {
    return optionalPrivateTokenChallenge();
  }

  const valid = await issuerPublicKey.verify(token);
  const fresh = await replayStore.markIfUnused(token);

  if (valid && fresh) {
    return trustedClientResponse();
  }

  return legacyFraudCheckResponse();
}

关键点:

  • 第 1 行:所有请求先进入同一个处理入口。
  • 第 2 行:服务器从请求头读取客户端带回的认证信息。
  • 第 4-6 行:没有 token 时返回可选挑战,不阻塞主页面加载。
  • 第 8 行:用 issuer 的公钥验证签名。
  • 第 9 行:记录 token 是否已经用过,防止重放。
  • 第 11-13 行:token 有效且未被使用时,把请求视为更可信。
  • 第 15 行:验证失败时回退到旧的反欺诈流程。

这里的关键是把 token 当成一个可选信任信号,保留旧客户端可用的降级路径。旧客户端不会响应 challenge,所以主页面加载不能依赖它(10:23)。

App 侧:URLSession 和 WebKit 自动处理

10:39)网站通过 Safari 和 WebKit 访问服务器时,可以自动使用 Private Access Tokens。App 也可以直接受益。

要求有三个:设备运行 iOS 16 或 macOS Ventura;设备登录了 Apple ID;app 使用 WebKit 或 URLSession 通过 HTTP 联系服务器。Apple ID 只用于 attestation,不会分享给接收 token 的服务器(10:49)。

URLSession 的处理方式最简单:app 不需要显式调用某个新 API。当前台 app 收到 PrivateToken challenge 时,系统会自动把 token 作为认证响应发回服务器(11:25)。

let url = URL(string: "https://example.com/account")!
let (data, response) = try await URLSession.shared.data(from: url)

if let httpResponse = response as? HTTPURLResponse,
   httpResponse.statusCode == 401 {
    // Conceptual fallback: the token challenge was received,
    // but the system could not fetch a Private Access Token.
    showFallbackVerification()
}

关键点:

  • 第 1 行:app 访问自己的服务器。
  • 第 2 行:使用 URLSession 发起 HTTP 请求;系统会自动处理支持的 PrivateToken challenge。
  • 第 4-7 行:如果取 token 失败,app 可能收到包含 challenge 的 401 响应。
  • 第 8 行:此时应进入适合业务的降级验证流程。

失败场景包括 app 不在前台,或设备没有登录 Apple ID。收到 401 并不表示协议不可用,它给 app 一个机会:根据自己的场景决定如何降级(11:41)。


核心启发

1. 登录页减少 CAPTCHA 打断

  • 做什么:在登录流程中把 Private Access Tokens 作为优先验证方式。
  • 为什么值得做:session 展示了 iOS 16 设备可以绕过传统 CAPTCHA 直接登录,减少正常用户被打断的次数。
  • 怎么开始:选择 Fastly、Cloudflare 或现有反欺诈服务作为 issuer,在登录接口返回可选 PrivateToken challenge,并在失败时保留原 CAPTCHA。

2. 注册接口增加一次性 token 防重放

  • 做什么:把账号注册接口的反欺诈判断拆成 token 验证和 replay check 两步。
  • 为什么值得做:Apple 明确指出 token 是一次性使用,服务器需要防止客户端重复提交同一个 token。
  • 怎么开始:在 challenge 中加入唯一值,或在服务端保存已使用 token;验证通过后再创建账号。

3. App 内 Web 登录复用系统能力

  • 做什么:把 app 内网页登录或账号绑定流程放在 WebKit,或用 URLSession 访问自己的认证接口。
  • 为什么值得做:WebKit 和 URLSession 可以自动响应 PrivateToken challenge,app 不需要手写令牌获取流程。
  • 怎么开始:确保请求发生在前台;对 401 响应做降级处理,比如显示现有验证码或人工验证入口。

4. 逐步替换第三方 CAPTCHA 脚本

  • 做什么:先在低风险路径上把 Private Access Tokens 接入现有 CAPTCHA 或反欺诈 provider。
  • 为什么值得做:演讲建议 challenge 可以由现有 provider 自动集成,也可以由服务器直接发出,便于渐进迁移。
  • 怎么开始:选择一个登录后操作、评论提交或试用注册页面,把 token 验证结果作为风险评分输入,而不是立即删除所有旧校验。

关联 Session

评论

GitHub Issues · utterances