category
安全上下文:此功能仅在安全上下文(HTTPS)、某些或所有支持的浏览器中可用。
Web身份验证API(WebAuthn)是凭证管理API的扩展,可通过公钥加密实现强身份验证,实现无密码身份验证和无SMS文本的安全多因素身份验证(MFA)。
注意:密钥是web身份验证的一个重要用例;有关实现的详细信息,请参阅为无密码登录创建密钥【 Create a passkey for passwordless logins】和通过表单自动填充使用密钥登录【 Sign in with a passkey through form autofill】。另请参阅Google Identity>Passwordless login with passkeys。
WebAuthn的概念和用法
WebAuthn使用非对称(公钥)加密技术代替密码或短信文本来注册、验证和对网站进行多因素验证。这有一些好处:
- 防止网络钓鱼:创建虚假登录网站的攻击者无法以用户身份登录,因为签名会随着网站的来源而更改。
- 减少了数据泄露的影响:开发人员不需要对公钥进行散列,如果攻击者能够访问用于验证身份验证的公钥,则无法进行身份验证,因为它需要私钥。
- 易受密码攻击:一些用户可能会重复使用密码,攻击者可能会获取其他网站的用户密码(例如通过数据泄露)。此外,文本密码比数字签名更容易使用暴力。
许多网站已经拥有允许用户注册新帐户或登录现有帐户的页面,WebAuthn可以替代或增强系统的身份验证部分。它扩展了凭证管理API【 Credential Management API,】,抽象了用户代理和身份验证程序之间的通信,并提供了以下新功能:
- 当navigator.credentials.create()与publicKey选项一起使用时,用户代理会通过验证器创建新的凭据,用于注册新帐户或将新的非对称密钥对与现有帐户关联。
- 注册新帐户时,这些凭据存储在服务器(也称为服务或依赖方)上,随后可用于用户登录。
- 非对称密钥对存储在验证器中,然后该验证器可以用于例如在MFA期间向依赖方验证用户。验证器可以嵌入到用户代理、操作系统(如Windows Hello)中,也可以是物理令牌(如USB或蓝牙安全密钥)。
- 当navigator.certificate.get()与publicKey选项一起使用时,用户代理使用一组现有的凭据向依赖方进行身份验证(作为主登录名,或在如上所述的MFA期间提供附加因素)。
在最基本的形式中,create()和get()都从服务器接收一个非常大的随机数,称为“challenge”,并将私钥签名的challenge返回给服务器。这向服务器证明,用户拥有身份验证所需的私钥,而不会在网络上泄露任何机密。
注意:“挑战”必须是一个大小至少为16字节的随机信息缓冲区。
创建密钥对并注册用户
为了说明凭据创建过程是如何工作的,让我们描述一下当用户想要向依赖方注册凭据时发生的典型流程:
- 依赖方服务器使用适当的安全机制(例如Fetch或XMLHttpRequest)将用户和依赖方信息连同“质询”一起发送到处理注册过程的web应用程序。
- 注意:依赖方服务器和网络应用程序之间共享信息的格式取决于应用程序。推荐的方法是将JSON类型的表示对象交换为凭据和凭据选项。在PublicKeyCredential中创建了方便的方法,用于将JSON表示转换为身份验证API所需的形式:parseCreationOptionsFromJSON()、parseRequestOptionsFromJSON()和
PublicKeyCredential.toJSON()
。
- 注意:依赖方服务器和网络应用程序之间共享信息的格式取决于应用程序。推荐的方法是将JSON类型的表示对象交换为凭据和凭据选项。在PublicKeyCredential中创建了方便的方法,用于将JSON表示转换为身份验证API所需的形式:parseCreationOptionsFromJSON()、parseRequestOptionsFromJSON()和
- web应用程序代表依赖方,通过navigator.credentials.create()调用,通过验证器启动新凭据的生成。此调用传递一个publicKey选项,指定设备功能,例如,设备是否提供自己的用户身份验证(例如,生物识别)。典型的create()调用可能如下所示:
JS let credential = await navigator.credentials.create({ publicKey: { challenge: new Uint8Array([117, 61, 252, 231, 191, 241, ...]), rp: { id: "acme.com", name: "ACME Corporation" }, user: { id: new Uint8Array([79, 252, 83, 72, 214, 7, 89, 26]), name: "jamiedoe", displayName: "Jamie Doe" }, pubKeyCredParams: [ {type: "public-key", alg: -7} ] } });
create()调用的参数与经过签名的SHA-256哈希一起传递给验证器,以确保其不会被篡改。
- 在验证器【authenticator】获得用户同意后,它生成一个密钥对,并将公钥和可选的签名证明返回给web应用程序。当create()调用返回的Promise以PublicKeyCredential对象实例的形式(PublicKeyCredential.response属性包含证明信息)实现时,会提供此选项。
- web应用程序再次使用适当的机制将PublicKeyCredential转发到服务器。
- 服务器存储公钥和用户标识,以记住凭据,以便将来进行身份验证。在此过程中,它会执行一系列检查,以确保注册完整且未被篡改。其中包括:
- 验证质询是否与发送的质询相同。
- 确保原产地是预期的原产地。
- 验证签名和证明是否使用了最初用于生成密钥par的验证器的特定模型的正确证书链。
警告:认证为依赖方提供了一种确定验证器来源的方法。依赖方不应试图维护身份验证程序的允许列表。
对用户进行身份验证
在用户向WebAuthn注册后,他们可以使用该服务进行身份验证(即登录)。身份验证流程与注册流程类似,主要区别在于身份验证:
- 不需要用户或依赖方信息
- 使用先前为服务生成的密钥对而不是验证器的密钥对创建断言。
典型的身份验证流程如下:
- 依赖方生成一个“质询”,并使用适当的安全机制将其与依赖方和用户凭据的列表一起发送给用户代理。它还可以指示在哪里查找凭据,例如,在本地内置验证器上,或通过USB、BLE等在外部验证器上。
- 浏览器要求验证器通过navigator.certificate.get()调用对质询进行签名,该调用在publicKey选项中传递凭据。典型的get()调用可能如下所示:
JS
let credential = await navigator.credentials.get({
publicKey: {
challenge: new Uint8Array([139, 66, 181, 87, 7, 203, ...]),
rpId: "acme.com",
allowCredentials: [{
type: "public-key",
id: new Uint8Array([64, 66, 25, 78, 168, 226, 174, ...])
}],
userVerification: "required",
}
});
get()调用的参数被传递给验证器以处理身份验证。
- 如果验证器包含给定的凭据之一,并且能够成功签署质询,则在收到用户同意后,它会向web应用程序返回一个已签名的断言。当get()调用返回的Promise以PublicKeyCredential对象实例的形式(PublicKeyCredential.response属性包含断言信息)实现时,会提供此选项。
- web应用程序将签名的断言转发到依赖方服务器,供依赖方验证。验证检查包括:
- 使用在注册请求期间存储的公钥来验证验证器的签名。
- 确保验证器签名的质询与服务器生成的质询匹配。
- 正在检查依赖方ID是否为此服务所需的ID。
- 一旦由服务器验证,则认为身份验证流是成功的。
控制对API的访问
WebAuthn的可用性可以使用权限策略来控制,该策略特别指定了两个指令:
- publickey-credentials-create:使用publickey选项控制navigator.credentials.create()的可用性。
- publickey-credentials-get:使用publickey选项控制navigator.credentials.get()的可用性。
这两个指令都有一个默认的allowlist值“self”,这意味着默认情况下,这些方法可以在顶级文档上下文中使用。此外,get()可以用于从与最顶端文档相同的来源加载的嵌套浏览上下文。get()和create()可以用于从不同来源加载到最顶端文档的嵌套浏览上下文(即在跨来源<iframes>中),如果公钥凭据分别允许get和公钥凭据create Permission Policy指令。对于跨原点create()调用,其中权限是由iframe上的allow=授予的,帧也必须具有瞬态激活。
注意:当策略禁止使用这些方法时,它们返回的promise将被NotAllowedError DOMException拒绝。
基本访问控制
如果您希望只允许访问特定的子域,您可以这样提供:
HTTP
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://subdomain.example.com")
允许在<iframe>中嵌入create和get()调用
如果您希望在<iframe>中使用get()或create()进行身份验证,需要遵循以下几个步骤:
- 嵌入依赖方网站的网站必须通过允许属性提供权限:
- 如果使用get():
HTML
<iframe
src="https://auth.provider.com"
allow="publickey-credentials-get *">
</iframe>
- 如果使用create():
HTML
<iframe
src="https://auth.provider.com"
allow="publickey-credentials-create 'self' https://a.auth.provider.com https://b.auth.provider.com">
</iframe>
如果create()被称为交叉原点,则<iframe>也必须具有瞬态激活。
- 依赖方网站必须通过“权限策略”标头为上述访问提供权限:
HTTP
Permissions-Policy: publickey-credentials-get=* Permissions-Policy: publickey-credentials-create=*
或者只允许特定的URL将依赖方网站嵌入<iframe>中:
HTTP
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://*.auth.provider.com")
接口
AuthenticatorAssertionResponse
向服务提供证据,证明验证器具有成功处理CredentialsContainer.get()调用发起的身份验证请求所需的密钥对。在get()Promise实现时获得的PublicKeyCredential实例的响应属性中可用。
AuthenticatorAttestationResponse
WebAuthn凭据注册的结果(即CredentialsContainer.create()调用)。它包含有关服务器执行WebAuthn断言所需的凭据的信息,例如其凭据ID和公钥。在create()Promise实现时获得的PublicKeyCredential实例的响应属性中可用。
AuthenticatorAttestationResponse和AuthenticatorAssertionResponse的基本接口。
提供有关公钥/私钥对的信息,该对是使用不可钓鱼和防数据泄露的非对称密钥对而不是密码登录服务的凭据。当通过create()或get()调用返回的Promise实现时获得。
对其他接口的扩展
CredentialsContainer.create()
, the publicKey
option
如上所述,使用publicKey选项调用create()将通过验证器启动新的非对称密钥凭据的创建。
CredentialsContainer.get()
, the publicKey
option
使用publicKey选项调用get()指示用户代理使用现有的一组凭据向依赖方进行身份验证。
示例
演示站点
- Mozilla Demo website and its source code.
- Google Demo website and its source code.
- WebAuthn.io demo website and its source code.
- github.com/webauthn-open-source and its client source code and server source code
用法示例
注意:出于安全原因,如果浏览器窗口在调用挂起时失去焦点,则会取消Web验证API调用(create()和get())。
JS
// sample arguments for registration
const createCredentialDefaultArgs = {
publicKey: {
// Relying Party (a.k.a. - Service):
rp: {
name: "Acme",
},
// User:
user: {
id: new Uint8Array(16),
name: "carina.p.anand@example.com",
displayName: "Carina P. Anand",
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7,
},
],
attestation: "direct",
timeout: 60000,
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x8c, 0x0a, 0x26, 0xff, 0x22, 0x91, 0xc1, 0xe9, 0xb9, 0x4e, 0x2e, 0x17,
0x1a, 0x98, 0x6a, 0x73, 0x71, 0x9d, 0x43, 0x48, 0xd5, 0xa7, 0x6a, 0x15,
0x7e, 0x38, 0x94, 0x52, 0x77, 0x97, 0x0f, 0xef,
]).buffer,
},
};
// sample arguments for login
const getCredentialDefaultArgs = {
publicKey: {
timeout: 60000,
// allowCredentials: [newCredential] // see below
challenge: new Uint8Array([
// must be a cryptographically random number sent from a server
0x79, 0x50, 0x68, 0x71, 0xda, 0xee, 0xee, 0xb9, 0x94, 0xc3, 0xc2, 0x15,
0x67, 0x65, 0x26, 0x22, 0xe3, 0xf3, 0xab, 0x3b, 0x78, 0x2e, 0xd5, 0x6f,
0x81, 0x26, 0xe2, 0xa6, 0x01, 0x7d, 0x74, 0x50,
]).buffer,
},
};
// register / create a new credential
navigator.credentials
.create(createCredentialDefaultArgs)
.then((cred) => {
console.log("NEW CREDENTIAL", cred);
// normally the credential IDs available for an account would come from a server
// but we can just copy them from above…
const idList = [
{
id: cred.rawId,
transports: ["usb", "nfc", "ble"],
type: "public-key",
},
];
getCredentialDefaultArgs.publicKey.allowCredentials = idList;
return navigator.credentials.get(getCredentialDefaultArgs);
})
.then((assertion) => {
console.log("ASSERTION", assertion);
})
.catch((err) => {
console.log("ERROR", err);
});
规格
Web Authentication: An API for accessing Public Key Credentials - Level 3
# iface-pkcredential
- 登录 发表评论