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场景实现安全、便捷的无密码认证。

发表评论 取消回复