浏览器里接 PublicKeyCredential,想少放长期刷新令牌还要踩哪些坑?

想少在浏览器里堆长期 refresh,又想把密码登录往下压,现在常摸到的入口是 Web Authentication API 里的 PublicKeyCredential。轮换能缩小「偷到一枚就能长期换」的窗口。可只要还有一枚长寿命 refresh 躺在前端,泄露面就还在。access 过期只是常规换发,真正拖着的往往是那枚刷新令牌能活多久。

MDN 上这条接口在干什么

MDN 的 PublicKeyCredential 页写得很明白。这套接口描述的是一对公钥/私钥凭证,用来替代密码登录。接口继承自 Credential,属于 Web Authentication 对 Credential Management 的扩展。页面标了 Secure context,也就是多数支持浏览器里要在 HTTPS(或本地安全上下文)里用。

注册走 navigator.credentials.create(),认证走 navigator.credentials.get()。两边都是挑战–响应:服务端下发随机 challenge,认证器在设备侧签名或生成证明,浏览器把结果交回给页面,再由页面送给服务端校验。私钥按这套模型不出设备。服务端手里主要是公钥、凭证 ID,以及自己维护的会话与撤销状态。

MDN 还写了两点容易漏看的限制。一是 限顶层上下文,塞在 iframe 里调用不会生效——嵌登录页、嵌支付页时很容易踩这里。二是浏览器支持面已经比较广,但个别能力(平台认证器探测、条件中介等)仍要按兼容表核对,别默认「所有机器都能弹本机生物识别」。

对照 refresh token rotation:轮换解决的是「长 refresh 被偷以后还能用多久」。通行密钥走的是另一条路:登录时尽量别再依赖一枚长期躺在前端的 refresh。能上通行密钥,不代表服务端可以不做会话、不做吊销、不做异常登录处置。前端少存一点敏感长令牌,和「账号绝对丢不了」中间还隔着好几层。两套可以对照威胁模型看,不必揉成一篇教程。(若站内已有 refresh 轮换帖,发布后可在此补站内互链。)

常见坑,多半卡在调用时机和环境

  • HTTPS 没到位:文档写明 Secure context。本地 localhost 一般还能试,预发如果还是裸 HTTP,create / get 直接不可用。
  • 用户手势:有的环境或版本里,页面一加载就调认证器会弹窗失败或行为怪异。更稳的做法是把调用绑在明确的点击、触摸上,别假设所有浏览器都允许静默弹。
  • RP ID 和站点源对不上:challenge 校验过了,用户却发现「这台设备登记过的凭证在子域用不了」,多半是依赖方标识和实际访问源没对齐,要按你们域名策略重做登记,而不是在前端硬编码绕过。
  • 跨设备同步:凭证跟平台账号、认证器型号、允不允许云同步绑在一起,Android、iOS、桌面浏览器各自一套。别在产品文案里写成「换台手机一定还能用同一枚」。
  • 服务端会话仍要自己管:通行密钥证明的是「这次挑战对上了」,登录态怎么发、多久过期、怎么强制下线,还是业务自己的事。短会话加可撤销刷新,和「完全扔掉令牌体系」不是同一句话。
  • 别拿云厂商控制台点几下当集成文档:真正要对的是挑战格式、RP ID、用户句柄、以及你们自己的会话表。控制台截图过半年就过时。

也不要写成「上了 Passkey 就不会被盗号」。钓鱼面小了、数据库拖库拖不到私钥,这些是这套模型相对密码的好处。设备失窃、会话固定、服务端逻辑漏洞、社会工程,都不在这句承诺的范围内。

你们有没有在 SPA 里试过用通行密钥把 refresh 的寿命压短,还是仍然双轨并行?卡在哪一步可以回帖里说现象。