---

title: Passkeys 与 WebAuthn 无密码登录:注册、验证与安全实践

tags: [WebAuthn, Passkeys, FIDO2, 公钥凭证, navigator.credentials]

description: 通过 WebAuthn/Passkeys 实现无密码注册与登录,完整覆盖前端创建与断言、后端验证、兼容回退与安全治理,并提供经验证的成功率与耗时指标。

categories:

  • 应用软件
  • 安全杀毒

---

背景与价值

  • 无密码登录提升安全与体验,杜绝密码泄露与钓鱼风险。
  • Passkeys 跨设备同步(平台密码管理器)使登录更顺滑,降低支持成本。

注册流程(前端)

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, '');
}

登录流程(前端)

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.challengeorigintype,解析 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 登录稳定,凭证发现路径无异常。

应用场景

  • 账户登录、支付确认、企业内网强身份校验、开发者/管理员高安全入口。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部