Passkey(通行密钥)让用户通过设备上的解锁方式登录网站。对开发者来说,核心变化是:服务器保存公钥,登录时验证一次性挑战的签名,而不是接收用户输入的密码。指纹、面容或设备 PIN 用于本地授权,不会作为 WebAuthn 响应发送给网站。
这份文档面向已经了解 HTTP、JavaScript 和基本登录流程的开发者。目标是读懂 WebAuthn 的安全边界,理解注册与登录的数据流,并通过一个可运行的示例把浏览器 API 和服务器验证连接起来。
这份中文文档与英文原文的关系#
你提供的链接是 W3C 的 Web Authentication Level 2,发布于 2021 年 4 月 8 日。它是一份规范,涉及浏览器、认证器和网站服务器的行为;它并非一篇按现代 Passkey 产品体验组织的开发教程。
本书将原规范中与网站开发最相关的部分重新编排,提供中文整理、关键语义译解、字段说明及新增代码讲解。这是非官方的学习版本,不是逐条完整译本;原规范的算法细节、符合性条款和所有证明格式仍应查阅英文。正文中的“译解”保留原意,其他解释、示例、取舍建议属于本书补充。出处、修改说明和文档使用规则见参考资料与版权说明。
截至本书核对日期 2026 年 10 月 16 日,WebAuthn Level 3 已于 2026 年 8 月 25 日成为 W3C Recommendation。下文以你指定的 Level 2 为基础;同步通行密钥、条件式界面和新的 JSON 辅助方法等现代内容,会明确标为补充,避免把后来的能力误认为 2021 年原文已有的要求。
先理解三个层次#
| 层次 | 解决的问题 | 你主要接触的内容 |
|---|---|---|
| Passkey 用户体验 | 如何让用户创建、选择、使用通行密钥 | 注册入口、系统选择器、账户恢复、凭据管理 |
| WebAuthn | 网页如何请求创建凭据、如何获得认证断言 | navigator.credentials.create()、navigator.credentials.get() |
| 认证器与通信机制 | 私钥由谁保护、如何与客户端协作 | 平台认证器、安全密钥;外部认证器可能通过 CTAP 等机制通信 |
不要把“Passkey”理解为某个 JavaScript 库,也不要把“FIDO2”理解为一条独立的 HTTP 登录接口。WebAuthn 负责浏览器侧的标准接口和相关协议要求;你仍然要实现服务器端的挑战管理、响应验证和会话管理。
FIDO 对 Passkey 的说明包含同步型和设备绑定型凭据。是否能够同步、同步到哪些设备、如何恢复,是凭据提供者与平台能力的一部分;网站不能仅凭浏览器 create() 成功,就推断凭据一定已经备份到云端。FIDO 官方 Passkey 介绍
原规范导读:两种 ceremony#
“Ceremony”在这里译为“交互流程”:它不仅是一次密码学运算,还包括服务器请求、浏览器判断来源、认证器与用户交互以及服务器最终校验。
**§1 引言译解:**网站通过两种相关流程使用 WebAuthn。注册时,认证器为该网站创建公钥凭据,并把它关联到当前用户的账户;认证时,网站接收认证断言,以验证注册该凭据的用户参与并同意了本次操作。英文原文 §1 Introduction
把它改写成开发任务:
- 注册:先确认“给哪个账户添加凭据”,再请求生成密钥;验证响应后保存凭据 ID、公钥与账户的关系。
- 登录:先发出新的随机挑战,再验证已有私钥对本次上下文的签名;验证全部通过后建立登录会话。
这里的“注册凭据”不等于“新建用户”。一个已有账户可以添加多个凭据;用户也可以先创建账户,再完成通行密钥注册。网站必须确定这两步之间的可信账户绑定,不能任由客户端在完成注册时提交另一个账户 ID。
建议学习路线#
| 阶段 | 阅读内容 | 完成后应能解释 |
|---|---|---|
| 建立模型 | 术语与安全边界、注册与认证协议 | RP ID、origin、UP/UV,以及谁持有私钥 |
| 接入浏览器 | 注册详解、认证详解 | 创建与获取凭据所需字段,JSON 与二进制转换 |
| 接入服务器 | 服务端验证章节与完整示例 | 为什么不能仅凭浏览器返回成功就登录 |
| 上线前检查 | 安全、恢复、测试与部署章节 | 挑战的一次性、域名绑定、账户恢复与真实设备验证 |
| 深入规范 | 原文 §6、§8、§9、§10 | 认证器数据结构、证明格式和扩展机制 |
如果你是在已有网站中增加登录方式,优先完成“已登录用户添加凭据 → 验证通行密钥登录 → 凭据管理与恢复”这条闭环。这样可以先解决账户绑定,再研究无用户名登录和自动填充等交互。
一个最小心智模型#
假设网站为 https://login.example.com,用户是“小林”。
注册后,服务器保存类似以下信息:
账户: user_42
用户句柄: 随机的、不含邮箱的字节序列
凭据 ID: 认证器生成的凭据标识
凭据公钥: 与该凭据对应的验证公钥
计数器等状态: 验证器返回并按策略维护再次登录时,服务器生成随机挑战 challenge_A。浏览器请求认证器对绑定到该网站的凭据进行操作。认证器授权后产生签名;服务器使用之前保存的公钥,连同挑战、来源、RP ID 等一起验证。
如果攻击者复制上一轮响应,challenge_A 已失效,不能用于新一轮登录。如果攻击者搭建另一个域名的钓鱼网站,标准客户端会根据调用来源限制它可使用的 RP ID。防重放依赖服务器正确管理挑战,防钓鱼依赖完整执行来源与 RP 绑定校验,两者都不能靠前端 UI 替代。
Passkey 没有替你完成什么#
你仍然需要决定“谁能添加或删除一个凭据”“丢失所有凭据后如何恢复”“成功认证后如何保护会话”。通行密钥减少密码泄露、复用和钓鱼风险,但恶意脚本、会话窃取、薄弱的账户恢复流程和错误的服务器验证仍可能让账户被接管。
静态文档可以发布到 Cloudflare Pages,浏览器也能在 HTTPS 页面调用 WebAuthn;真正的多用户登录还需要可信服务器或边缘函数、持久化存储和安全会话。本书中的学习网站与可运行示例会清楚区分这两个层次。
读原文时的定位方法#
W3C 原文会用 MUST、SHOULD、MAY 区分要求强度。可分别理解为“必须”“应当”“可以”,不要把 SHOULD 当成可随意忽略的备注。原文也明确标注某些部分为非规范性说明;用例和示例帮助理解,实现验证规则要回到规范性算法。
网站开发者最值得反复对照的是§7 WebAuthn Relying Party Operations,而不是照抄 §1 的演示代码。原文给出的§1.1 阅读路线同样强调了这一点。
学习检查#
create()成功后,为什么服务器还必须验证响应?- 服务器拥有公钥之后,为什么仍需每次生成不同的 challenge?
- 为什么“给已有账户增加凭据”通常比从零设计无密码开户更适合作为第一步?
带着这三个问题进入下一章。它们对应的是信任边界、重放防护和账户绑定,也是最容易被“几行前端代码”掩盖的核心问题。