导语:把“秘密”从服务器移到用户手里的硬件
密码的问题根源在于"服务器知道秘密":数据库泄露就是凭证泄露,钓鱼页面也能骗走密码。无密码认证的思路是不再共享秘密——用户持有的是一把私钥,服务器只保存公钥。WebAuthn 把这个构想变成浏览器原生标准,Passkey 让它在多设备间可同步、可恢复。
一句话总结: WebAuthn = 浏览器原生公钥认证协议;Passkey = WebAuthn 凭证的可同步形态。服务器不再存储任何可被冒充的共享秘密,钓鱼与中间人攻击自然失效。
1. 从密码到公钥凭证的演进
1.1 密码认证的固有缺陷
| 缺陷 | 表现 |
|---|---|
| 服务器存储秘密 | 数据库泄露 = 凭证泄露 |
| 密码可被钓鱼 | 伪造页面骗取密码 |
| 密码可被猜测 | 弱密码、撞库、喷洒 |
| 密码可被复用 | 一处泄露牵连多处 |
| 无法绑定来源 | 密码在哪个站用都一样 |
1.2 公钥凭证如何破解这些缺陷
注册时:
用户的认证器(安全密钥/手机/系统钥匙串)生成一对密钥
私钥:永不出设备,只在本机签名
公钥:发给服务器保存
登录时:
服务器给一个随机挑战(challenge)
认证器用私钥对挑战签名
服务器用公钥验签,确认"私钥确实在用户手里"
对比密码:
密码:服务器存"能冒充你的东西",泄露即被盗
公钥:服务器只存"能验证你的东西",泄露也冒充不了
一句话总结: 公钥认证把风险从"服务器存秘密"转移到"私钥永不出设备"——数据库泄露不再等于账号被盗,这是无密码最根本的价值。
2. FIDO2 协议栈与核心概念
2.1 FIDO2 与 WebAuthn 及 CTAP2
| 组件 | 作用 |
|---|---|
| WebAuthn | 浏览器/平台的标准 API,与 Relying Party 交互 |
| CTAP2 | 浏览器与外部认证器(USB/蓝牙安全密钥)的传输协议 |
| 认证器 | 生成与保管密钥的设备:平台认证器(手机)或漫游认证器 |
| Relying Party | 依赖方,即你的 Web 服务 |
协议分层(示意):
Web 应用(RP) ↔ WebAuthn API ↔ 浏览器
│
CTAP2/平台层
│
认证器(私钥保管处)
公钥凭证注册与认证都在这条链上完成,
整个过程中服务器始终只接触公钥与签名结果。
2.2 关键数据结构 PublicKeyCredential
注册时产生:
id → 凭证 ID(对应认证器里那把私钥)
response.attestationObject → 注册信息(含公钥、认证器元数据)
response.clientDataJSON → 包含来源 origin、挑战 challenge
登录时产生:
id → 凭证 ID
response.authenticatorData → 认证器输出(含签名计数、来源)
response.signature → 私钥对挑战的签名
response.clientDataJSON → 包含 origin 与挑战
一句话总结: FIDO2 是一套完整协议栈,WebAuthn 负责浏览器侧,CTAP2 负责外部认证器;所有结构都以"公钥 + 签名"为中心,服务器永不接触私钥。
3. 注册流程详解
3.1 服务端发起注册
// 服务端生成注册选项(伪代码)
function registrationOptions(user) {
return {
challenge: generateRandomChallenge(), // 一次性随机挑战
rp: { id: "example.com", name: "示例站" }, // 依赖方标识
user: {
id: encodeUserId(user.id), // 用户唯一标识
name: user.username,
displayName: user.displayName
},
pubKeyCredParams: [
{ type: "public-key", alg: -7 }, // ES256
{ type: "public-key", alg: -257 } // RS256
],
timeout: 60000,
authenticatorSelection: {
residentKey: "preferred", // 是否需要可发现凭证
userVerification: "preferred" // 是否要求用户验证(指纹等)
}
};
}
3.2 前端调用创建凭证
// 浏览器侧注册
const options = await fetchRegisterOptions();
const publicKey = preparePublicKeyOptions(options);
const credential = await navigator.credentials.create({
publicKey: publicKey
});
// 把结果交给服务端验签与存储
await fetch('/api/webauthn/register/verify', {
method: 'POST',
body: JSON.stringify({
id: credential.id,
rawId: arrayToBase64(credential.rawId),
type: credential.type,
response: {
attestationObject: arrayToBase64(credential.response.attestationObject),
clientDataJSON: arrayToBase64(credential.response.clientDataJSON)
}
})
});
3.3 服务端验证要点
注册验证必须检查:
① clientDataJSON 里的 origin 必须等于你声明的站点来源
② challenge 必须等于刚才发的那一个,且一次性
③ 解析 attestationObject,取出公钥与凭证 ID
④ 按安全级别决定是否验证"认证器证明"(attestation)
⑤ 存储:凭证 ID + 公钥 + 签名计数器(初始值)
常见坑:
· 忘记校验 origin/challenge → 凭证可被跨站注册
· 挑战可复用 → 重放攻击
· 公钥不校验算法白名单 → 可能接受弱算法
一句话总结: 注册流程的关键是服务端严格验证 origin 与 challenge,并把公钥、凭证 ID、签名计数器安全存储——这步把关不严,后续认证流程的信任就无从谈起。
4. 认证流程详解
4.1 服务端发起认证
// 服务端生成认证选项(伪代码)
function authenticationOptions(user) {
return {
challenge: generateRandomChallenge(),
rpId: "example.com",
allowCredentials: [ // 允许的凭证白名单
{ id: getCredentialId(user), type: "public-key" }
],
userVerification: "preferred",
timeout: 60000
};
}
4.2 前端调用获取断言
// 浏览器侧认证
const options = await fetchAuthOptions();
const credential = await navigator.credentials.get({
publicKey: prepareAuthOptions(options)
});
await fetch('/api/webauthn/login/verify', {
method: 'POST',
body: JSON.stringify({
id: credential.id,
type: credential.type,
response: {
authenticatorData: arrayToBase64(credential.response.authenticatorData),
signature: arrayToBase64(credential.response.signature),
clientDataJSON: arrayToBase64(credential.response.clientDataJSON)
}
})
});
4.3 服务端验签流程
认证验证必须检查(按顺序):
① 凭证 ID 是否存在于库中
② clientDataJSON 的 origin 与 challenge 校验
③ 从 authenticatorData 解析来源 rpIdHash,必须等于你的站点
④ 用存储的公钥验证 signature
⑤ 检查签名计数器:新计数必须大于旧计数(防克隆)
一次性挑战 + 签名:即使攻击者截获认证响应,
也无法重放,因为 challenge 已失效、origin 不匹配。
一句话总结: 认证流程本质是**“挑战 + 私钥签名 + 公钥验签”**;origin 绑定杀死钓鱼,一次性 challenge 杀死重放,签名计数器防克隆——三重校验缺一不可。
5. 对抗钓鱼与中间人攻击
5.1 为什么钓鱼拿不到有用的凭证
钓鱼页面伪造 example.com 的登录页,诱导用户"验证":
真 WebAuthn:
认证器签名的是"挑战 + 真实 origin(example.com)"
钓鱼页 origin 是 attacker.com,验签必然失败
私钥绑定站点,绝不替 attacker.com 签名
对比密码:
钓鱼页只需拿到密码字符串,到真站就能用
WebAuthn 的凭证永远绑死一个 rpId,跨站即失效
5.2 对抗中间人 MITM
| 攻击方式 | WebAuthn 为什么能防 |
|---|---|
| 劫持 DNS 指向假站 | origin 校验让假站验签失败 |
| 中间人改请求 | 签名覆盖 origin 与 challenge,篡改即失效 |
| 重放认证响应 | 一次性挑战过期,重放被拒 |
| 证书伪造 | 浏览器通道 + 来源绑定双重保护 |
5.3 仍需注意的边界
· 恶意软件本地键盘记录:键盘记录器拿不到私钥,但可能截获其他输入
· 用户恶意授权:社会工程诱骗用户"亲自"在真站签名,仍需用户意识
· 认证器自身安全:受信任平台与设备须保持系统更新
· 恢复过程:找回流程若退化为"邮箱 + 密码",仍是攻击入口
一句话总结: WebAuthn 的**来源绑定(origin)+ 一次性挑战(challenge)**从协议层面让钓鱼与中间人失效;但社会工程、设备本身的安全,仍要靠用户教育与端侧防护补齐。
6. 落地工程的用户体验与恢复
6.1 体验设计要点
| 要点 | 做法 |
|---|---|
| 渐进增强 | 先支持,再推荐;不强制一步到位 |
| 登录默认 | 有 Passkey 优先 Passkey,无则回落 |
| 生物识别 | 平台认证器体验最优(指纹/面容) |
| 失败降级 | 认证失败给出清晰提示与替代路径 |
| 一次性注册 | 注册成功即引导绑定备用方式 |
6.2 恢复与备用凭证策略
无密码最大的工程风险是"用户换了设备就登不进去"。
恢复策略:
① 多凭证:同一账号绑定多个认证器(手机 + 安全密钥)
② 同步通行密钥:用系统云同步(iCloud/Google)保留 Passkey
③ 备用因子:保留 TOTP/恢复码作为降级手段
④ 可验证的找回流程:找回必须经过同等强度的验证
提示:
· 不要设计"通过密码重置"这种与无密码背道而驰的后门
· 恢复码一次性、加密存储,与凭证强度匹配
6.3 一个落地校验的检查清单
□ origin 校验严格,站点来源白名单
□ challenge 一次性、过期短
□ 签名计数器持久化并递增校验
□ 支持多凭证管理与展示
□ 提供备用登录途径与恢复流程
□ 日志审计:认证成功/失败、凭证变更留痕
□ 合规:生物识别数据本地处理,不入服务端
一句话总结: 落地难点不在协议,而在体验与恢复——把多凭证、Passkey 同步、备用因子设计好,用户才愿意放弃密码;任何"方便的重置后门"都会重新打开钓鱼大门。
7. 多设备同步与兼容性
7.1 设备同步的两种形态
| 形态 | 说明 | 优点 | 局限 |
|---|---|---|---|
| 漫游认证器 | USB/蓝牙安全密钥随身带 | 强隔离、跨设备 | 需要额外硬件 |
| 平台认证器 + 同步 | 手机系统同步 Passkey | 体验好、跨设备 | 依赖生态同步 |
7.2 Passkey 同步的信任模型
Passkey 同步(如 iCloud 钥匙串、Google 密码管理器):
私钥在设备上生成,通过端到端加密同步到同一生态的其他设备
服务器仍是只存公钥,同步只在用户自己的生态内发生
企业/高安全场景:
可关闭"同步 Passkey",强制使用独立漫游密钥
用策略控制:是否允许可发现凭证、是否要求用户验证
通过强制验证(userVerification: required)保证"人在设备前"
7.3 兼容性现实
· 平台覆盖:主流浏览器均已支持 WebAuthn,但历史版本有差异
· 降级路径:老浏览器/老设备保留密码或 TOTP 兜底
· 证书方案:内部系统可借助 FIDO2 设备实现无密码桌面登录
· 迁移成本:从密码迁移建议"先并跑、再逐步收紧"
一句话总结: 多设备同步让 Passkey 走向大众,也带来生态绑定与信任模型选择——高安全场景关同步、强制验证;一般场景享受同步便利,但要保留降级路径。
8. 总结
| 环节 | 关键动作 |
|---|---|
| 凭证 | 私钥永不出设备,服务器只存公钥 |
| 注册 | 严格校验 origin 与一次性挑战 |
| 认证 | 验签 + 签名计数器 + 来源绑定 |
| 抗钓鱼 | origin 绑定让伪造站点验签失败 |
| 落地 | 多凭证、Passkey 同步、备用因子 |
| 兼容 | 老设备降级路径、渐进迁移 |
一句话记住:无密码不是"消灭口令",而是"消灭服务器上的秘密"——把信任从可泄露的共享秘密,换成永远不离开用户设备的私钥,钓鱼与中间人的土壤自然消失。配上良好的体验与恢复设计,Passkey 才能从"酷炫"变成"真正好用"。
延伸阅读
- 身份认证与会话安全 — MFA 演进与无密码的关系
- IAM 与零信任身份体系 — 凭证体系在零信任中的定位
- 零信任网络架构 — 身份为边界的访问控制
- XSS 与 CSRF 防御实战 — WebAuthn 也依赖安全的 Web 通道
- 密码管理与密钥安全 — 服务端密钥保管的对比思考
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。