3. WebAuthn核心架构
要真正理解Passkey的安全边界,必须先拆开WebAuthn的架构。WebAuthn不是单一软件,而是一套由浏览器、操作系统、硬件认证器与业务服务器共同履约的协议。它把“证明用户是谁”从密码比对,转变为“证明用户持有某个私钥”。这个转变的关键,在于四个角色和一条挑战-响应链路。
一、四个核心角色与公钥凭证
WebAuthn规范定义了三个直接参与方,加上一个承载标准的上层联盟:
- 依赖方(Relying Party,RP):即你的业务服务器,例如银行App后端。RP负责生成挑战、调用WebAuthn API、验证签名,并保存公钥凭证。RP的域名(RP ID)是安全边界,通常为注册域的eTLD+1,如
example.com。 - 用户代理(User Agent,UA):浏览器或WebView。UA负责调用平台API、传递挑战、协调认证器,并执行同源策略与RP ID校验。UA不接触私钥,只做“传令兵”和“守门人”。
- 认证器(Authenticator):真正生成和保存密钥的实体。它可以是平台认证器(Touch ID、Windows Hello、Android Keystore),也可以是跨平台认证器(YubiKey等安全密钥)。认证器内部有不可导出的私钥,并可能要求生物识别或PIN解锁。
- FIDO2标准:由W3C WebAuthn与FIDO联盟CTAP2共同组成。WebAuthn定义浏览器与RP之间的JS API;CTAP2定义浏览器与外部认证器之间的通信协议。两者合起来,才是完整的FIDO2认证能力。
注册成功后,RP保存的不是密码,而是公钥凭证(Public Key Credential)。它包含凭证ID、公钥、签名计数器、AAGUID等元数据。私钥永远留在认证器内,即使RP数据库被拖库,攻击者也无法伪造签名。
核心结论:WebAuthn的安全模型是“私钥不出认证器,公钥可公开存储”。RP验证的是签名,而不是秘密本身。
二、挑战-响应模型:一次完整认证的流程
挑战-响应是WebAuthn的骨架。RP每次认证都生成一个随机、一次性、有时效的challenge,认证器用私钥对“挑战+客户端数据+认证器数据”做签名,RP再用公钥验签。攻击者重放旧签名无效,因为挑战已变。
- 用户点击“使用Passkey登录”,RP生成
challenge并返回给前端。 - 前端调用
navigator.credentials.get(),传入publicKey参数,包括challenge、rpId、allowCredentials等。 - UA校验RP ID与当前源匹配,弹出认证器选择界面。
- 认证器要求用户验证(生物识别/PIN),用私钥签名
authenticatorData + SHA-256(clientDataJSON)。 - RP收到
AuthenticatorAssertionResponse,校验挑战、源、RP ID哈希、签名和计数器。 - 校验通过则建立会话,否则拒绝。
// 前端发起认证的核心参数(简化)
const publicKey = {
challenge: base64urlToBuffer(serverChallenge),
rpId: "example.com",
allowCredentials: [{
id: base64urlToBuffer(credentialId),
type: "public-key",
transports: ["internal", "hybrid"]
}],
userVerification: "preferred",
timeout: 60000
};
const assertion = await navigator.credentials.get({ publicKey });
// assertion.response.signature 发送给RP验签
注册流程与认证对称:RP下发challenge和用户信息,认证器生成新密钥对,返回AttestationObject和clientDataJSON,RP验证后保存公钥凭证。注册时的attestation可用于确认认证器型号,但Passkey场景通常采用none以保护隐私。
三、FIDO2标准关系与典型场景
很多团队把WebAuthn、FIDO2、CTAP2混为一谈。下表给出清晰边界:
| 标准/组件 | 作用范围 | 典型载体 | 是否直接处理私钥 |
|---|---|---|---|
| WebAuthn | RP与UA之间的JS API | 浏览器、WebView | 否 |
| CTAP2 | UA与外部认证器之间的协议 | USB/NFC/蓝牙安全密钥 | 否 |
| 平台认证器 | 操作系统内置密钥管理 | Touch ID、Windows Hello | 是(内部) |
| RP服务器 | 挑战生成、验签、凭证存储 | 业务后端 | 否(仅存公钥) |
典型场景:用户在手机上用Face ID注册Passkey,RP保存公钥。换到笔记本登录时,浏览器通过混合传输(hybrid)调用手机上的认证器,手机弹出Face ID确认,签名后回传。整个过程中,私钥从未离开手机安全芯片,笔记本和服务器都只看到签名结果。
四、常见误区与复盘要点
误区一:把RP ID当成普通配置。RP ID一旦上线就不能随意更改,否则所有已注册凭证失效。它必须是当前源的可注册域后缀,且不能是IP地址。
- 误区二:忽略挑战的随机性与时效。挑战必须由密码学安全随机数生成,且一次性使用。若复用或过期时间过长,重放攻击窗口会打开。
- 误区三:只验签名不验来源。必须校验
clientDataJSON.origin与预期源一致,并校验rpIdHash,否则钓鱼站点可能诱导签名。 - 误区四:认为Passkey等于无密码。Passkey可以作为多因素之一,也可以配合PIN。是否“无密码”取决于RP的认证策略,而非协议本身。
- 误区五:忽视计数器与克隆检测。部分认证器提供签名计数器。若计数器回退,可能意味着凭证被克隆,应触发风控或吊销。
复盘建议:在实现时,把“挑战生成、验签、源校验、RP ID哈希校验、计数器校验”做成五个独立可测试的单元。任何一步缺失,都会让WebAuthn退化为“带签名的密码”。