3. WebAuthn核心概念
要真正把WebAuthn用进支付强认证与风控体系,必须先厘清它的六个核心概念:公钥凭证、挑战-响应、RP ID、用户句柄、认证器,以及客户端与依赖方的角色分工。这六个概念不是孤立术语,而是一条从注册到认证、从设备到服务端的完整信任链。理解它们之间的约束关系,才能避免“接入了WebAuthn却依然被钓鱼”的典型失败。
一、核心原理:一次注册,长期持有私钥
WebAuthn的本质是基于非对称密钥的凭证模型。注册阶段,认证器在本地生成一对密钥,私钥永不离开认证器,公钥则交给依赖方(Relying Party,RP)保存。此后每次认证,RP发出随机挑战,认证器用私钥签名,RP用保存的公钥验签。整个过程没有共享密钥在网络中传输,因此天然抗钓鱼、抗重放。
这里的关键是公钥凭证(Public Key Credential)。它不是一个简单的公钥字符串,而是一个包含凭证ID、公钥、签名计数器、AAGUID、传输方式等字段的结构体。RP保存的是“凭证ID + 公钥 + 用户绑定关系”,认证时通过凭证ID定位公钥。凭证ID本身不是秘密,可以公开传输,真正敏感的是认证器内部的私钥。
二、挑战-响应:防重放的核心机制
挑战-响应(Challenge-Response)是WebAuthn防重放攻击的根基。RP在每次认证请求中生成一个高熵随机数作为challenge,认证器将其纳入签名数据。攻击者即使截获了上一次的签名,也无法用于下一次,因为challenge已变化。
签名数据并非只签challenge,而是签一个由多字段拼接后哈希的认证数据(authenticatorData)与客户端数据JSON(clientDataJSON)的组合。clientDataJSON中包含type、challenge、origin等,RP必须逐项校验:challenge是否匹配、origin是否属于自己、type是否为webauthn.get或webauthn.create。
// 服务端生成挑战(示例)
const challenge = crypto.randomBytes(32); // 32字节高熵随机数
session.challenge = challenge.toString('base64url');
// 认证器返回的clientDataJSON(解码后)
{
"type": "webauthn.get",
"challenge": "k9X...base64url...",
"origin": "https://pay.example.com",
"crossOrigin": false
}
三、RP ID、用户句柄与作用域约束
RP ID是WebAuthn的作用域标识,通常是一个有效域名(如example.com)。它决定了凭证的适用范围:在pay.example.com注册的凭证,默认可用于同域及其子域,但不能用于evil.com。浏览器会强制校验origin与RP ID的一致性,这是WebAuthn抗钓鱼的关键——钓鱼站点即使诱导用户操作,也无法让认证器为错误RP ID签名。
用户句柄(user.id)是RP为用户分配的、不可猜测的字节序列,用于在认证器中标识用户。它不是用户名,也不是邮箱。用户名可以变、可以重复展示,但user.id必须稳定且唯一。认证器保存的是user.id与凭证的映射,RP在注册时提供,认证时通过allowCredentials回传凭证ID来定位。
| 概念 | 作用域/归属 | 是否秘密 | 典型错误 |
|---|---|---|---|
| RP ID | 依赖方域名 | 否 | 用IP或端口,导致浏览器拒绝 |
| user.id | 依赖方分配 | 否(但需不可猜测) | 直接用邮箱或自增ID |
| challenge | 每次会话 | 是(一次性) | 固定值或复用 |
| 凭证ID | 认证器生成 | 否 | 当作秘密存储 |
| 私钥 | 认证器内部 | 是 | 试图导出到服务端 |
四、认证器、客户端与依赖方的角色分工
WebAuthn定义了三个角色,理解它们的分工是排障的基础:
- 认证器(Authenticator):真正持有私钥并执行签名的实体。可以是平台认证器(Touch ID、Windows Hello、Android生物识别),也可以是漫游认证器(YubiKey等安全密钥)。它负责用户验证(UV)和用户存在性检测(UP)。
- 客户端(Client):通常是浏览器或操作系统WebAuthn API。它负责与认证器通信、组装clientDataJSON、校验origin与RP ID,并把结果返回给RP。客户端不持有私钥,但它是信任链的“守门人”。
- 依赖方(Relying Party,RP):即支付系统服务端。它生成challenge、保存公钥凭证、验签、校验计数器与标志位,并最终决定是否放行交易。
在支付风控场景中,RP还需要关注认证器返回的标志位:UP(用户存在)、UV(用户验证)、BE(备份可用)、BS(备份状态)。例如,大额支付通常要求UV=true,即用户通过了生物识别或PIN;若仅UP=true(如按下按键),则可能只适合低风险场景。风控引擎可据此动态调整策略。
五、案例与复盘:一次“验签通过却被盗刷”的复盘
某支付团队接入WebAuthn后,验签逻辑正确,但仍发生盗刷。复盘发现:服务端在注册时把user.id直接设为用户邮箱,且认证时未校验签名计数器。攻击者通过撞库拿到邮箱后,在另一台设备上重新注册,由于user.id相同,RP错误地将新凭证与老用户绑定,导致攻击者用自己的认证器完成了“合法”签名。修复方案是:user.id改为不可猜测的随机UUID,并在认证时校验计数器单调递增,同时绑定凭证ID与用户的一对多关系。
这个案例说明:WebAuthn的密码学强度很高,但身份绑定与状态校验仍由RP负责。公钥凭证、挑战、RP ID、用户句柄、认证器、客户端与RP角色,任何一个环节的语义被误用,都会把强认证降级为“形式上的强认证”。在支付风控实战中,建议把上述六要素做成一张校验清单,纳入每次认证请求的必检项,并结合交易金额、设备指纹、地理位置做动态策略,才能真正发挥WebAuthn的抗钓鱼与抗重放价值。