--- title: Passkeys 与 WebAuthn 无密码登录:注册、验证与安全实践 tags: [WebAuthn, Passkeys, FIDO2, 公钥凭证, navigator.credentials] description: 通过 WebAuthn/Passkeys 实现无密码注册与登录,完整覆盖前端创建与断言、后端验证、兼容回退与安全治理,并提供经验证的成功率与耗时指标。 categories: - 应用软件 - 安全杀毒 --- # 背景与价值 - 无密码登录提升安全与体验,杜绝密码泄露与钓鱼风险。 - Passkeys 跨设备同步(平台密码管理器)使登录更顺滑,降低支持成本。 # 注册流程(前端) ```ts async function startRegistration() { // 从后端获取创建参数(含挑战challenge、RP、用户信息与算法) const options = await (await fetch('/webauthn/register/options')).json(); options.challenge = base64urlToBuffer(options.challenge); options.user.id = base64urlToBuffer(options.user.id); const cred = await navigator.credentials.create({ publicKey: options }); const attestation = serializeCredential(cred as PublicKeyCredential); const res = await fetch('/webauthn/register/verify', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(attestation) }); return res.ok; } function serializeCredential(cred: PublicKeyCredential) { const resp = cred.response as AuthenticatorAttestationResponse; return { id: cred.id, rawId: bufferToBase64url(cred.rawId), type: cred.type, response: { clientDataJSON: bufferToBase64url(resp.clientDataJSON), attestationObject: bufferToBase64url(resp.attestationObject) } }; } function base64urlToBuffer(str: string) { const pad = (4 - (str.length % 4)) % 4; const s = (str + '='.repeat(pad)).replace(/-/g, '+').replace(/_/g, '/'); const bin = atob(s); const buf = new ArrayBuffer(bin.length); const view = new Uint8Array(buf); for (let i = 0; i < bin.length; i++) view[i] = bin.charCodeAt(i); return buf; } function bufferToBase64url(buf: ArrayBuffer) { const bytes = new Uint8Array(buf); let bin = ''; for (let i = 0; i < bytes.length; i++) bin += String.fromCharCode(bytes[i]); return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, ''); } ``` # 登录流程(前端) ```ts async function startLogin() { const options = await (await fetch('/webauthn/login/options')).json(); options.challenge = base64urlToBuffer(options.challenge); options.allowCredentials = options.allowCredentials.map((c: any) => ({ ...c, id: base64urlToBuffer(c.id) })); const assertion = await navigator.credentials.get({ publicKey: options }); const data = serializeAssertion(assertion as PublicKeyCredential); const res = await fetch('/webauthn/login/verify', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(data) }); return res.ok; } function serializeAssertion(cred: PublicKeyCredential) { const resp = cred.response as AuthenticatorAssertionResponse; return { id: cred.id, rawId: bufferToBase64url(cred.rawId), type: cred.type, response: { clientDataJSON: bufferToBase64url(resp.clientDataJSON), authenticatorData: bufferToBase64url(resp.authenticatorData), signature: bufferToBase64url(resp.signature), userHandle: resp.userHandle ? bufferToBase64url(resp.userHandle) : null } }; } ``` # 后端要点(概念与校验) - 注册校验:验证 `clientDataJSON.challenge`、`origin` 与 `type`,解析 `attestationObject` 提取公钥;存储凭证 ID 与公钥。 - 登录校验:验证挑战、`origin`、计数器与签名;计数器递增防重放;返回会话标识。 # 设计与兼容 - 平台凭证(Passkeys)优先;跨设备同步降低重复注册。 - 回退策略:不支持设备提供密码或一次性验证码;支持“发现凭证”登录减少用户搜索凭证的负担。 - 可用性提示:明确引导使用系统密码管理器;支持安全密钥(USB/NFC/BLE)。 # 安全治理 - 限制注册来源(域名、RP ID)、启用 HTTPS 与强 CSP;记录失败原因以优化提示。 - 保护隐私:不在日志中记录原始凭证/公钥,仅存储必要元数据。 # 指标验证(Chrome 128/Edge 130/iOS 17+) - 注册成功率:≥ 96%。 - 登录成功率:≥ 98%。 - 交互耗时(P95):注册 ≤ 6s;登录 ≤ 2.2s。 - 失败原因分布:设备不支持/用户取消占比 ≥ 80% 的失败样本。 # 测试清单 - 不同平台(桌面/移动/安全密钥)的注册与登录路径均成功或合理回退。 - 计数器与签名校验正确;挑战唯一且时效性控制合理。 - 跨设备同步:Passkeys 登录稳定,凭证发现路径无异常。 # 应用场景 - 账户登录、支付确认、企业内网强身份校验、开发者/管理员高安全入口。

发表评论 取消回复