WebAuthn/FIDO2 无密码认证最佳实践概述WebAuthn/FIDO2通过公钥注册与挑战签名实现无密码认证,提升安全性与用户体验。本文提供服务端与前端的最小实现与治理建议。注册流程(前端)async function startRegistration(username: string) { const res = await fetch('/webauthn/register/options', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username }) }) const options = await res.json() options.challenge = Uint8Array.from(atob(options.challenge), c => c.charCodeAt(0)) options.user.id = Uint8Array.from(atob(options.user.id), c => c.charCodeAt(0)) const cred = await navigator.credentials.create({ publicKey: options }) const att = (cred as PublicKeyCredential) const payload = { id: att.id, rawId: btoa(String.fromCharCode(...new Uint8Array(att.rawId as ArrayBuffer))), type: att.type, response: { clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(att.response.clientDataJSON as ArrayBuffer))), attestationObject: btoa(String.fromCharCode(...new Uint8Array(att.response.attestationObject as ArrayBuffer))) } } await fetch('/webauthn/register/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) } 登录流程(前端)async function startLogin(username: string) { const res = await fetch('/webauthn/login/options', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ username }) }) const options = await res.json() options.challenge = Uint8Array.from(atob(options.challenge), c => c.charCodeAt(0)) options.allowCredentials = options.allowCredentials.map((c: any) => ({ ...c, id: Uint8Array.from(atob(c.id), d => d.charCodeAt(0)) })) const assertion = await navigator.credentials.get({ publicKey: options }) const cred = assertion as PublicKeyCredential const payload = { id: cred.id, rawId: btoa(String.fromCharCode(...new Uint8Array(cred.rawId as ArrayBuffer))), type: cred.type, response: { clientDataJSON: btoa(String.fromCharCode(...new Uint8Array(cred.response.clientDataJSON as ArrayBuffer))), authenticatorData: btoa(String.fromCharCode(...new Uint8Array((cred.response as AuthenticatorAssertionResponse).authenticatorData as ArrayBuffer))), signature: btoa(String.fromCharCode(...new Uint8Array((cred.response as AuthenticatorAssertionResponse).signature as ArrayBuffer))), userHandle: btoa(String.fromCharCode(...new Uint8Array(((cred.response as any).userHandle || new ArrayBuffer(0)) as ArrayBuffer))) } } await fetch('/webauthn/login/verify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) } 服务端要点(伪代码)// 生成挑战并存储到会话 function issueChallenge(): string { const bytes = crypto.randomBytes(32) return bytes.toString('base64') } // 验证注册:解析attestationObject并保存公钥(credentialId, publicKey, counter) // 验证登录:校验challenge与签名、counter递增、防重放 运维要点在HTTPS下启用,前端与后端使用随机挑战并防重放绑定设备与账户,记录AAGUID与计数器用于风险检测对账号恢复提供备用登录(MFA/备份代码),并限制重置路径通过WebAuthn/FIDO2的挑战与签名流程,可在Web场景实现安全、便捷的无密码认证。

发表评论 取消回复