← 首页 / 用一对钥匙替掉密码 待审核
passkey · WebAuthn 全流程

用一对钥匙替掉密码

passkey(通行密钥)不是「更安全的密码」,而是彻底换了模型——密码是两端都存的共享秘密(会被钓走、撞库、泄库),passkey 是一对非对称密钥:私钥被锁在你设备的 authenticator 里永不离开,服务器只存公钥;登录靠对服务端发来的 challenge 做一次签名来证明「我手里有私钥」。底层是 FIDO2 = WebAuthn(浏览器 JS API navigator.credentials+ CTAP2(浏览器↔认证器协议)。

本页每节都直接调浏览器原生 PublicKeyCredential / navigator.credentials——能力检测是当场跑出来的真实结果,「真机尝试」会触发本机 Touch ID / Windows Hello(localhost 是 secure context,WebAuthn 可用)。四节:密码的缺陷与支持注册一把新钥匙三个正交的旋钮用钥匙登录

1 · 密码的根本缺陷,与 passkey 的不同模型

why · 模型与支持

密码的根本问题不在「不够长」,而在它是共享秘密(shared secret):同一个值你和服务器两端都要存。于是它能被钓鱼页面骗走、能在 A 站泄露后拿去对 B 站撞库、服务器一旦被脱库就整批泄露。passkey 换了一套数学:非对称密钥对——私钥锁在你设备里永不外传,服务器只拿到公钥,就算泄库也没有可冒用的秘密

1.1 · 共享秘密 vs 非对称密钥

抗钓鱼是「天生」的,不靠用户警惕。 每个 passkey 在创建时就绑定了 rp.id(域名)。浏览器只会把 example.com 的凭证用在 example.com 上——你被骗到 examp1e.com 的钓鱼站,那里的 JS 根本调不出example.com 的 passkey。密码做不到这点:再像的假站,用户仍可能手动输入密码。

1.2 · FIDO / WebAuthn / CTAP 是什么关系

这几个词常被混用,其实是一摞分层标准,由 FIDO Alliance(Fast IDentity Online)与 W3C 共同制定。「passkey」是这套技术面向用户的品牌名:

名字 管什么
FIDO2 总称 = WebAuthn + CTAP2。passkey 就是 FIDO2 凭证「可发现 + 可同步」时的用户叫法。
网页 ↔ 浏览器 WebAuthn W3C 的浏览器 JS API:navigator.credentials.create() / .get()PublicKeyCredential。本系列讲的正是它。
浏览器 ↔ 认证器 CTAP2 Client to Authenticator Protocol:浏览器怎么和外接安全钥 / 手机(USB·NFC·BLE)对话。网页层看不到,但「扫码用手机登录电脑」靠它。

1.3 · 你这台机器现在支持吗(当场检测)

下面四项都是真的调浏览器 API 跑出来的。WebAuthn 要求 secure context(HTTPS 或 localhost);isUserVerifyingPlatformAuthenticatorAvailable() 告诉你本机有没有内建认证器(Touch ID / Windows Hello);isConditionalMediationAvailable() 决定能不能做表单自动填充式登录。

1.4 · 跨平台支持矩阵

passkey 已是各大平台默认能力,差异主要在「能不能跨设备同步」。数据概览自 passkeys.dev(FIDO Alliance 维护):

「同步」让 passkey 在你所有设备上都在(换手机不丢);硬件钥换来更高保证级但不可同步。
平台 创建 passkey 云端同步 当 cross-device 认证器(扫码)
Apple(iOS 16+ / macOS 13+) iCloud Keychain
Android(9+ / Play services) Google Password Manager
Windows(10/11 · Hello) 11 起逐步同步
Chrome / Edge / Safari / Firefox 借底层 OS / 密码管理器
硬件安全钥(YubiKey 等) ✗(不同步,绑在钥上) 本身就是 cross-platform

1.5 · 在 Chrome 里管理已有 passkey

想看 / 删本机存了哪些 passkey,Chrome 的入口层级较深(得先用 passkey 登录过一次,管理页才会出现):

地址栏直接输入 chrome://settings/passkeys 也能到。手机上则在系统设置里:iOS =「密码」、Android =「Google 密码管理工具」。删掉本机 passkey 不会注销服务器侧的公钥——那是两份独立的东西,服务器侧要去各站「安全设置」里单独移除。

2 · 注册一把新钥匙

register · create()

注册(在 WebAuthn 里叫 attestation / registration)做的事:让 authenticator 当场生成一对新密钥,把公钥连同一个新 credential id 交回服务器存档,私钥留在设备里。入口是 navigator.credentials.create({ publicKey })——关键全在 publicKey 这个 PublicKeyCredentialCreationOptions 里。

2.1 · 一次注册握手

2.2 · 拼一份 creation options(改开关看 JSON)

下面几个开关实时改写 publicKey。把鼠标移到某个字段说明上,JSON 里对应那行会点亮:

三个旋钮 authenticatorSelection 这节先用默认值。 它里面的 authenticatorAttachment / residentKey / userVerification 是三件互相独立的事,单独成一节讲——见 三个正交的旋钮

2.3 · 真机尝试:在这台机器上真注册一把

点下面的按钮会用上面的选项真的调 create()——本机有 Touch ID / Windows Hello 的话会弹出生物识别。rp.id 取当前域名,challengecrypto.getRandomValues 现场生成。成功后凭证 id 会被存进 localStorage,用钥匙登录一节能直接拿去登录。

为什么 challenge / user.idBufferSource 而不是字符串? WebAuthn 选项里凡是「二进制数据」(challenge、id)都用 ArrayBuffer / Uint8Array。服务端通常发 base64url 字符串,前端要先 decodeBufferSource 再塞进选项——新接口 PublicKeyCredential.parseCreationOptionsFromJSON() 能帮你自动转。

3 · 三个最容易混淆的旋钮

selection · 三个正交维度

注册时 authenticatorSelection 里有三个字段,初学者几乎都会把它们当成「一件事的不同说法」。其实它们互相独立、各自调节——回答的是三个不同问题:认证器在哪、凭证存哪、要不要验人。下面三个旋钮可任意组合,看每种组合意味着怎样的登录体验。

3.1 · authenticatorAttachment——认证器在哪

控制让用户用「什么位置」的认证器。注意它不决定验不验人,也不决定凭证存哪:

取值 含义 例子
platform 绑在本机的内建认证器,不可拔。 Touch ID / Face ID / Windows Hello / Android 指纹
cross-platform 可移动、能接入其他设备的认证器。 USB / NFC 安全钥(YubiKey)、扫码借用的另一台手机
(不填) 两者都允许,把选择权交给用户。 大多数面向公众的网站不填

3.2 · residentKey / discoverable credential——凭证存哪

决定凭证是不是 discoverable credential(可发现凭证,旧称 resident key)——即把凭证本体存进 authenticator、并且能在不被告知 id 的情况下被列举出来。它直接决定登录能不能免输用户名:

取值 凭证存在哪 对登录的影响
required 存进 authenticator(占一个槽位)。 可发现 → 登录时 allowCredentials留空,authenticator 自己列出账号 → usernameless。passkey 的典型选择。
preferred 能存就存(看 authenticator)。 尽量做成可发现,设备不支持则退回不可发现。
discouraged 不存本体,只在服务器存「加密包」(server-side credential)。 不可发现 → 登录前服务器必须按用户名查出 credential id,放进 allowCredentials 递给浏览器。

「可发现」到底发现了什么? discoverable credential 在 authenticator 里连带存了 rp.iduser 信息,所以浏览器能问它「你有哪些 example.com 的账号」并列出供用户选择——这就是为什么能不先报用户名就登录。代价是它占用 authenticator 的存储槽(硬件密钥的槽位有限)。

3.3 · userVerification——要不要验人(安全等级 LoA / AAL)

控制 authenticator 要不要额外证明「现在按的人就是设备主人」(生物识别 / PIN)。它是安全等级的旋钮——决定这次认证算「单因子(只证明握有设备)」还是「双因子(握有设备 + 通过验证)」,和凭证存哪完全无关:

取值 要求 得到的保证
required 必须验人(指纹 / 面容 / PIN),不通过就失败。 双因子:掌握设备 + 验证本人。passkey 默认期望。
preferred 能验就验,不能验也放行。 视设备而定(默认值)。
discouraged 只验在场(碰一下 user presence),不验身份。 单因子。老式 U2F 二次因子安全钥常这样。

正交性一句话:「验不验人(userVerification)」属于安全等级 / AAL;「凭证存不存得进设备(residentKey)」属于存储与可发现性;「认证器在本机还是外接(authenticatorAttachment)」属于物理位置。三件事可以任意搭配——上面三个旋钮就是用于验证这一点。

3.4 · 几种典型组合

场景 attachment residentKey userVerification
现代 passkey(本机免密登录) platform required required
面向公众、不限设备 (不填) preferred preferred
老式 U2F 二次因子(密码之外再加一把安全钥) cross-platform discouraged discouraged
高保证(企业 / 金融) cross-platform required required

4 · 用钥匙登录

authenticate · get()

登录(WebAuthn 里叫 assertion)做的事:服务器发一个新 challenge,authenticator 用已有私钥给它签个名,服务器用注册时存的公钥验签。入口是 navigator.credentials.get({ publicKey })——全程没有任何秘密被发送,只发一个签名。

4.1 · 一次登录握手

4.2 · 拼一份 request options

把鼠标移到某个字段说明上,JSON 里对应那行会点亮:

allowCredentials 留空 = usernameless 登录的关键。 留空时浏览器去问 authenticator「你存了哪些本站的 discoverable credential」,直接列给用户挑——用户不用先输用户名。填了列表则是「先知道你是谁,再让你证明」的流程(凭证可以是 non-discoverable 的 server-side credential)。

旧设备的限制: 部分老 authenticator 不支持空 allowCredentials,直接抛出 Use of an empty 'allowCredentials' list is not supported on this device。想兼容这类设备就得回落到「先收用户名 → 服务器查出 id → 填进 allowCredentials」的传统流程,或用 isConditionalMediationAvailable() 等检测做能力分支。

4.3 · 真机尝试:用注册一节创建的钥匙登录

先到注册一节建一把(凭证 id 会存进 localStorage),回到这里点登录就会触发本机生物识别、真的走一遍 get() 并验出一个 signatureallowCredentials 选「空」则改走 discoverable 流程。

4.4 · Conditional UI:把 passkey 接进输入框自动填充

体验最顺畅的登录不需要点任何按钮:页面一加载就以 mediation:'conditional' 在后台挂一个 get(),配合输入框上的 autocomplete="username webauthn",用户点输入框时系统下拉里直接列出 passkey,选了就登录。它不会主动弹模态框,没有可用凭证时悄无声息:

4.5 · 跨设备:hybrid(扫码用手机登录电脑)

电脑上没存 passkey 时,可借手机里的那把:浏览器弹一个二维码,手机扫码后通过蓝牙就近确认身份——这就是 hybrid transport(旧称 caBLE),底层走 CTAP2。代码侧只要在 get() 里给 hints: ['hybrid'] 把它提到优先,或在 allowCredentials 的条目里标 transports: ['hybrid']。私钥始终不离开手机,电脑只拿到一次签名。

相关链接

  • Web Authentication: An API for accessing Public Key Credentials W3C WebAuthn Level 3 规范:PublicKeyCredential 接口、create / get 的全部选项(authenticatorSelectionattestationhints…)与算法的权威定义。
  • MDN · Web Authentication API developer.mozilla.org WebAuthn 总览:注册 / 登录两条流程、discoverable credential、autofill UI 的概念图与 navigator.credentials.create() / .get() 的整体用法。
  • MDN · PublicKeyCredentialCreationOptions developer.mozilla.org 对应「注册」「三个旋钮」两节:rp / user / challenge / pubKeyCredParams / authenticatorSelection / attestation / excludeCredentials 逐字段说明与兼容性。
  • Can I use · Web Authentication API caniuse.com WebAuthn 的实时浏览器支持矩阵(逐版本细分),与本系列「当场检测」「跨平台支持」两节对照。
  • Create a passkey for passwordless logins web.dev 对应「注册」一节:服务端发选项 → 前端 create() → 回传公钥的完整握手,含 excludeCredentials 防重复注册的用法。
  • Sign in with a passkey through form autofill web.dev 对应「登录」一节:Conditional UI / form autofill —— mediation:'conditional' 配合 autocomplete="username webauthn" 把 passkey 接进原生输入框建议。
  • Passkey form autofill (Conditional UI) developer.chrome.com Chrome 侧的 Conditional UI 落地细节与 isConditionalMediationAvailable() 检测。
  • Discoverable credentials deep dive web.dev / FIDO 对应「三个旋钮」一节:discoverable credential(resident key)到底存了什么、为何能 usernameless,以及 residentKey 三档取值。
  • Passkey device support passkeys.dev 各 OS / 浏览器 / authenticator 的 passkey 支持矩阵(本系列「支持」表的数据来源);站点 passkeys.dev 由 FIDO Alliance 维护。
  • Passkeys demo Google Google 官方可交互 demo:真实注册与登录的完整流程,可与本系列的「真机尝试」对照。
  • Passwords suck. Can passkeys replace them? kerkour.com 对应「密码的缺陷」一节:从工程视角讲密码的系统性缺陷,以及 passkey 在抗钓鱼 / 抗泄库上的根本不同与现实阻力。