userVerification 深度解析

本文档讨论了 WebAuthn 中的 userVerification,以及在通行密钥创建或身份验证期间指定 userVerification 时产生的浏览器行为。

WebAuthn 中的“用户验证”是什么?

通行密钥基于公钥加密技术构建。创建通行密钥时,系统会生成公钥/私钥对,私钥由通行密钥提供方存储,公钥则会返回给信赖方 (RP) 的服务器进行存储。服务器可以使用配对的公钥验证由同一通行密钥签名的签名,从而对用户进行身份验证。公钥凭据上的“用户存在”标志 (UP) 可证明有人在身份验证期间与设备进行了互动。

用户验证是一种可选的安全层,旨在断言在身份验证期间在场的是正确的人,而不仅仅是某个人,正如用户在场断言所断言的那样。在智能手机上,这通常通过使用屏幕锁定机制来完成,无论是生物识别信息还是 PIN 码或密码。在通行密钥注册和身份验证期间,身份验证器数据中返回的“UV”标志会报告是否执行了用户验证

macOS 上 iCloud 钥匙串中的用户验证对话框的屏幕截图。该对话框会提示用户使用 Touch ID 登录,并显示请求身份验证的来源以及用户名。对话框右上角有一个标记为“取消”的按钮。
macOS 上 iCloud 钥匙串中的用户验证对话框。
Android 版 Chrome 中的用户验证对话框的屏幕截图。该对话框会提示用户使用人脸识别或指纹检测功能验证自己的身份,并显示请求进行身份验证的来源。左下角有一个使用 PIN 码进行验证的选项。
Android Chrome 上的用户验证对话框。

如何在服务器上验证 UP 和 UV

用户存在 (UP) 和用户验证 (UV) 布尔值标志会在身份验证器数据字段中发送到服务器。在身份验证期间,可以通过使用存储的公钥验证签名来验证身份验证器数据字段的内容。只要签名有效,服务器就可以认为标志是真实的。

身份验证数据结构的图示。从左到右,数据结构的每个部分依次为“RP ID HASH”(32 字节)、“FLAGS”(1 字节)、“COUNTER”(4 字节,大端字节序 uint32)、“ATTESTE CRED. DATA”(如果存在,则长度可变)和“EXTENSIONS”(如果存在,则长度可变 [CBOR])。“标志”部分已展开,显示了潜在标志的列表,从左到右依次标记为“ED”“AT”“0”“BS”“BE”“UV”“0”和“UP”。
公钥凭据中的身份验证器数据字段。

在通行密钥注册和身份验证时,服务器应根据要求检查 UP 标志是 true 还是 false,以及 UV 标志是 true 还是 false

指定 userVerification 参数

根据 WebAuthn 规范,RP 可以在凭据创建和断言时通过 userVerification 参数请求用户验证。它接受 'preferred''required''discouraged',分别表示:

  • 'preferred'(默认):首选在设备上使用用户验证方法,但如果该方法不可用,则可以跳过。如果执行了用户验证,响应凭据会包含 true 的 UV 标志值;如果未执行用户验证,则会包含 false 的 UV 标志值。
  • 'required':必须调用设备上可用的用户验证方法。如果没有可用的,则请求会在本地失败。这意味着,响应凭据始终返回,且 UV 标志设置为 true
  • 'discouraged':不建议使用用户验证方法。不过,根据设备的不同,系统可能仍会执行用户验证,并且 UV 标志可能包含 truefalse

用于创建通行密钥的示例代码:

const publicKeyCredentialCreationOptions = {
  // ...
  authenticatorSelection: {
    authenticatorAttachment: 'platform',
    residentKey: 'required',
    requireResidentKey: true,
    userVerification: 'preferred'
  }
};

const credential = await navigator.credentials.create({
  publicKey: publicKeyCredentialCreationOptions
});

通行密钥身份验证的示例代码:

const publicKeyCredentialRequestOptions = {
  challenge: /* Omitted challenge data... */,
  rpId: 'example.com',
  userVerification: 'preferred'
};

const credential = await navigator.credentials.get({
  publicKey: publicKeyCredentialRequestOptions
});

您应为 userVerification 选择哪个选项?

您应使用的 userVerification 值取决于应用要求以及用户体验需求。

何时使用 userVerification='preferred'

如果您优先考虑用户体验而非保护,请使用 userVerification='preferred'

在某些环境中,用户验证带来的不便多于保护。例如,在 macOS 上,如果 Touch ID 不可用(因为设备不支持、已停用或设备处于翻盖模式),系统会要求用户改为输入系统密码。这会造成摩擦,用户可能会完全放弃身份验证。如果您更注重消除摩擦,请使用 userVerification='preferred'

一张屏幕截图,显示了在 macOS 上当 Touch ID 不可用时出现的通行密钥对话框。该对话框包含请求身份验证的来源以及用户名等信息。对话框右上角有一个标记为“取消”的按钮。
当触控 ID 不可用时,macOS 上显示的通行密钥对话框。

对于 userVerification='preferred',如果成功执行用户验证,UV 标志为 true;如果跳过用户验证,UV 标志为 false。例如,在没有 Touch ID 的 macOS 上,它会要求用户点击某个按钮以跳过用户验证,并且公钥凭据包含 false UV 标志。

然后,UV 标志可作为风险分析中的信号。如果登录尝试因其他因素而显得有风险,您可能需要在未执行用户验证的情况下向用户显示额外的登录验证。

何时使用 userVerification='required'

如果您认为 UP 和 UV 都绝对必要,请使用 userVerification='required'

此选项的缺点是,用户在登录时可能会遇到更多阻碍。例如,在没有 Touch ID 的 macOS 上,系统会要求用户输入其系统密码。

借助 userVerification='required',您可以确保在设备上执行用户验证。确保服务器验证 UV 标志是否为 true

总结

通过利用用户验证,通行密钥信赖方可以评估设备所有者登录的可能性。他们可以选择是否要求用户验证,也可以根据回退登录机制对用户流程的影响程度来决定是否将用户验证设为可选。确保服务器检查通行密钥用户身份验证的 UP 标志和 UV 标志。