---
url: 'https://www.corbado.com/zh/blog/passkeys-cheat-sheet'
title: '开发者通行密钥速查表'
description: '关于 WebAuthn 和实施通行密钥的开发者指南。下载 PDF 格式的速查表，或者在此网站一站式获取所有信息。'
lang: 'zh'
author: 'Lukas R.'
date: '2026-07-03T07:11:53.489Z'
lastModified: '2026-07-03T07:12:55.128Z'
keywords: '通行密钥, WebAuthn, 开发者指南, 通行密钥速查表, 自动填充'
category: 'Passkeys Implementation'
---

# 开发者通行密钥速查表

## 免费下载

免费下载完整的**通行密钥速查表**并获取所有技术解析。

- ✅ 已有超过 4,000 次下载
- ✅ 应 Ally、Kmart、Octopus [Energy](https://www.corbado.com/passkeys-for-energy) 和 Stanford CS 开发团队的要求
- ✅ 没有营销套话 - 只有技术干货

## Key Facts

- 通行密钥身份验证使用两个**流程**：注册（证明）和登录（断言），每个流程都需要一个由服务器生成的、由身份验证器签名的随机质询。
- **PublicKeyCredentialCreationOptions** 管理通行密钥注册，而 **PublicKeyCredentialRequestOptions** 管理登录。这两个对象均在服务器端生成，并包含供身份验证器签名的质询。
- **Conditional UI** 将可用的通行密钥显示为自动填充建议，但这需要可发现凭据（常驻密钥），并且并非在所有操作系统和浏览器组合中都受支持。
- **依赖方 ID (rpID)** 将通行密钥绑定到域：仅当 URL 与 rpID 匹配或为其非公共后缀子域时，身份验证才会成功。
- 通行密钥使用 **COSE 算法**生成密钥，具体算法记录在证明对象的 parsedCredentialPublicKey 属性中。

## 1. WebAuthn 流程

使用通行密钥的身份验证基于两个过程（也称为流程）：**注册**（也称为**证明阶段**）和**登录**（也称为**断言阶段**）。<br/>
每个阶段都需要一个由服务器生成的随机质询，该质询由身份验证器签名后发送回 WebAuthn 服务器以验证用户身份。

### 1.1 注册（证明）

![WebAuthn 中注册流程的过程流](https://www.corbado.com/website-assets/65fad0076e7b4367976ee993_cs_1_1_registration_flow_min_26eb12d652.png)_注册流程使用两个核心对象：PublicKeyCredentialCreationOptions 和证明 (attestation)。_

[Watch on YouTube](https://www.youtube.com/watch?v=0hRYXHESA24)

### 1.2 登录（断言）

![WebAuthn 中登录流程的过程流](https://www.corbado.com/website-assets/65fad0f2e412e9ad9d2a5bf1_cs_1_2_login_flow_min_2aaefc04dd.png)_登录流程使用两个核心对象：PublicKeyCredentialRequestOptions 和断言 (assertion)。_

## 2. 重要对象

对于使用通行密钥的注册和登录，主要有四个对象：

- PublicKeyCredentialCreationOptions
- PublicKeyCredentialRequestOptions
- 证明 (attestation)
- 断言 (assertion)

本节还将解释 authenticatorSelection 对象，该对象在 PublicKeyCredentialCreationOptions 中使用。

### 2.1 公钥凭据创建选项 (Public Key Credential Creation Options)

PublicKeyCredentialCreationOptions 是证明阶段（注册）的核心对象。它由 [WebAuthn 服务器](#webauthn-server)创建并返回。

```json
{
    "PublicKeyCredentialCreationOptions": {
        "rp": {
            "id": "passkeys.eu",
            "name": "Corbado Passkeys Demo"
        },
        "user": {
            "displayName": "john.doe",
            "id": "dXNyLZ….DU10Tc",
            "name": "john@doe.com"
        },
        "challenge": "888fix4Bus...pHHr3Y",
        "pubKeyCredParams": [
            {
                "alg": -7,
                "type": "public-key"
            },
            {
                "alg": -257,
                "type": "public-key"
            }
        ],
        "excludeCredentials": [],
        "authenticatorSelection": {
            "authenticatorAttachment": "platform",
            "residentKey": "required",
            "userVerification": "required"
        },
        "attestation": "none",
        "extensions": {}
    }
}
```

该对象包含以下属性：

- **rp:** 标识依赖方（即寻求对用户进行身份验证的服务器），请参阅第 [4.2 依赖方 ID (rpID)](#relying-party-id) 节。
- **user:** 包含有关请求证明的用户帐户的数据。ID 是依赖方选择的字节序列，不得包含个人信息。用户名或电子邮件地址保存在 name 或 displayName 属性中。在第 [4.1 数据库架构](#database-schema) 节中了解更多信息。
- **challenge:** 需要由身份验证器签名的随机生成的 base64URL 编码的 BufferSource。
- **pubKeyCredParams:** 要创建的凭据的指定属性，通常是支持的算法。
- **timeout:** 客户端等待调用完成的可选时间（以毫秒为单位）。
- **excludeCredentials:** 用于限制在一台设备上创建多个通行密钥的可选凭据列表。
- **authenticatorSelection:** 该方法所用身份验证器的可选选择，例如是否需要 residentKey。请参阅 [2.5 authenticatorSelection](#authenticatorSelection)。
- **attestation:** 可用于请求将证明对象以特定形式传递给依赖方。可能的值为 none（默认）、indirect、direct 和 enterprise。
- **extensions:** 针对其他处理（例如特定返回值）的可选请求。例如：
    - _credProbs_ 请求有关创建的凭据是否可发现的信息
    - _prf_ 允许依赖方使用与凭据关联的伪随机函数 (PRF) 的输出

### 2.2 公钥凭据请求选项 (Public Key Credential Request Options)

PublicKeyCredentialRequestOptions 是断言阶段（登录）的核心对象。它由 [WebAuthn 服务器](#webauthn-server)创建并返回。

```json
{
    "publicKeyCredentialRequestOptions": {
        "challenge": "pT7HMA-…dFPHk",
        "timeout": 500,
        "rpId": "passkeys.eu",
        "userVerification": "preferred",
        "allowCredentials": [],
        "extensions": []
    }
}
```

该对象包含以下属性：

- **challenge、timeout、extensions:** 请参阅[上文](#creationoptions)。
- **rpId:** 断言请求的依赖方的标识符，请参阅第 [4.2 依赖方 ID (rpID)](#relying-party-id) 节。
- **allowCredentials:** 允许用于身份验证的可选凭据列表，按降序指示调用者的偏好。该列表将填充 PublicKeyCredentialDescriptors。
- **userVerification:** 用于指定操作期间用户验证要求的可选值。可能的值为 preferred（默认）、required 或 discouraged。

### 2.3 证明 (Attestation)

在证明/注册流程中，身份验证器会返回此**注册响应**。您可以在 [Passkeys Debugger](https://www.passkeys-debugger.io/) 中自行尝试。

```json
{
    "authenticatorAttachment": "platform",
    "id": "JKZbixUfKN_aZtimefYT-OjH5dw",
    "rawId": "JKZbixUfKN_aZtimefYT-OjH5dw",
    "response": {
        "attestationObject": {
            "fmt": "none",
            "attStmt": {},
            "authData": {
                "rpIdHash": "PpZrl-Wqt-OFfBpyy2SraN1m7LT0GZORwGA7-6ujYkM",
                "flags": {
                    "userPresent": true,
                    "userVerified": true,
                    "backupEligible": true,
                    "backupStatus": true,
                    "attestedData": true,
                    "extensionData": false
                },
                "counter": 0,
                "aaguid": {
                    "raw": "fbfc3007-154e-4ecc-8c0b-6e020557d7bd",
                    "name": "iCloud Keychain"
                },
                "credentialID": "JKZbixUfKN_aZtimefYT-OjH5dw",
                "credentialPublicKey": "pQECAyYgASFYIPWLalDzyxIDmAADvfK8iNM5To50kh7TyPH-teEz8RMdIlgg3D7bPIWQJ8z-WFn3zdYZzJw9c7mhPdmflQqD9vV7efA",
                "parsedCredentialPublicKey": {
                    "keyType": "EC2 (2)",
                    "algorithm": "ES256 (-7)",
                    "curve": 1,
                    "x": "9YtqUPPLEgOYAAO98ryI0zlOjnSSHtPI8f614TPxEx0",
                    "y": "3D7bPIWQJ8z-WFn3zdYZzJw9c7mhPdmflQqD9vV7efA"
                }
            }
        },
        "clientDataJSON": {
            "type": "webauthn.create",
            "challenge": "k2p6f-upzP_hc6NZvmMAxiI0VSTeQIeXXVRGW62LTj0",
            "origin": "https://www.passkeys-debugger.io",
            "crossOrigin": false
        },
        "transports": ["hybrid", "internal"],
        "authenticatorData": "PpZrl-Wqt-OFfBpyy2SraN1m7LT0GZORwGA7-6ujYkNdAAAAAPv8MAcVTk7MjAtuAgVX170AFCSmW4sVHyjf2mbYpnn2E_jox-XcpQECAyYgASFYIPWLalDzyxIDmAADvfK8iNM5To50kh7TyPH-teEz8RMdIlgg3D7bPIWQJ8z-WFn3zdYZzJw9c7mhPdmflQqD9vV7efA",
        "publicKey": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9YtqUPPLEgOYAAO98ryI0zlOjnSSHtPI8f614TPxEx3cPts8hZAnzP5YWffN1hnMnD1zuaE92Z-VCoP29Xt58A",
        "publicKeyAlgorithm": -7
    },
    "type": "public-key",
    "clientExtensionResults": {}
}
```

**证明**包含一些重要组件，如 [attestationObject](#attestationObject)、[algorithm](#algorithm) 和 [transport](#transport) 标志。

#### 2.3.1 attestationObject

![attestationObject 是 WebAuthn 中证明的一部分](https://www.corbado.com/website-assets/65fad76914987b84f3ecf4cd_cs_2_3_attestation_Object_min_12a3b9e5cc.png)

摘自 [W3C 的 WebAuthn 规范](https://www.w3.org/TR/webauthn-2/#attestation-object)

**attestationObject** 是一个 CBOR 编码对象，其中包含有关新创建的凭据、公钥和其他相关信息的数据：

- 对于通行密钥，**fmt** 通常评估为 "none"
- **attStmt** 对于通行密钥为空，对于其他身份验证器（例如硬件安全密钥）则会填充内容
- **authData** 是一个包含以下数据的缓冲区：

![authData 是一个缓冲区，包含标志、计数器和扩展等](https://www.corbado.com/website-assets/65fc7c70babae11893e3e1e1_cs_2_3_auth_Data_min_7a7d39d083.png)

详细了解[扩展](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API/WebAuthn_extensions)。

#### 算法 (algorithm)

通行密钥是使用 **COSE 算法**生成的，在[证明对象](#attestation)的 parsedCredentialPublicKey 的 **algorithm 属性**中指明了所使用的算法。<br/>
以下是相关的 COSE 算法的概述：

![通行密钥使用 COSE 算法生成](https://www.corbado.com/website-assets/65fade5ce80ed150f88b59fb_cs_2_3_COSE_algorithms_min_f0d1a5f931.png)

#### 2.3.2 传输 (transport)

**transports** 属性表示身份验证器可通过哪些机制与客户端通信。一些常见的示例值组合包括：

- **"transports": \["internal","hybrid"]**: 通行密钥可以从平台身份验证器（例如 Face ID、Touch ID、Windows Hello）使用，或者通过跨设备身份验证（使用二维码和蓝牙）使用。
- **"transports": \["internal"]**: 通行密钥只能从平台身份验证器（例如 Face ID、Touch ID、Windows Hello）使用。
- **未设置 "transports" 属性:** 默认行为，不给出任何指示。

### 2.4 断言 (Assertion)

在**断言**/登录流程中，身份验证器会返回此**登录响应**。您可以在 [Passkeys Debugger](https://www.passkeys-debugger.io/) 中自行尝试。

```json
{
    "id": "JKZbixUfKN_aZtimefYT-OjH5dw",
    "rawId": "JKZbixUfKN_aZtimefYT-OjH5dw",
    "type": "public-key",
    "authenticatorAttachment": "platform",
    "response": {
        "authenticatorData": {
            "rpIdHash": "PpZrl-Wqt-OFfBpyy2SraN1m7LT0GZORwGA7-6ujYkM",
            "flags": {
                "userPresent": true,
                "userVerified": true,
                "backupEligible": true,
                "backupStatus": true,
                "attestedData": false,
                "extensionData": false
            },
            "counter": 0
        },
        "clientDataJSON": {
            "type": "webauthn.get",
            "challenge": "GCVkITWbe2l2dttsn_DgJYvH9QPHPDo0ygWgcgI6B7U",
            "origin": "https://www.passkeys-debugger.io",
            "crossOrigin": false,
            "other_keys_can_be_added_here": "do not compare clientDataJSON against a template. See https://goo.gl/yabPex"
        },
        "signature": "MEQCIA-orC8N2KKWOxyY17BWP8lB-Be5to9btXRnJZf2SLhXAiBGxJe5Eu5LwOTbsyzAYmIXHOhlC3pN7s7Q1fRLvEW57g",
        "userHandle": "_FKz1uwqmR_3yGq6hJntzoIFwFC_d1u_53YRELh0KlE"
    }
}
```

**断言**包含一些重要组件，如标志 (flags)、签名 (signature) 和 userHandle。

#### 2.4.1 标志 (flags)

以下是最重要**标志**及其组合的概述：

![最重要的标志是 userPresent、userVerified、backupEligible、backupStatus](https://www.corbado.com/website-assets/65fc7ca5a6a6fb1f3e66b514_cs_2_4_flags_min_19e5ddc4ce.png)

#### 2.4.2 签名 (signature)

**签名**用于验证尝试登录的用户是否确实拥有私钥。签名是通过将 authenticatorData 和 clientDataHash（即 ClientDataJSON 的 SHA-256 版本）拼接在一起，并使用私钥（在身份验证器中）对结果进行签名来创建的。若要使用公钥进行验证，我们也拼接 authenticatorData 和 clientDataHash。如果验证结果返回 true，则身份验证成功。

![签名用于验证私钥](https://www.corbado.com/website-assets/65fae2ad96200e9576992209_cs_2_4_signature_min_36b9b8ccbe.png)

#### 2.4.3 userHandle

**userHandle** 是实际的 user_id。在第 [**4.1 数据库架构**](#database-schema) 节中了解有关 user_id 的更多信息。

### 2.5 authenticatorSelection

**authenticatorSelection** 对象允许服务器指示身份验证器和凭据创建的设置，包括以下值：

#### 2.5.1 authenticatorAttachment

- **Platform:** 身份验证器连接到客户端的平台，因此不可移除。
- **Cross-platform:** 身份验证器未绑定到客户端的平台，可以在多台设备上使用。

#### 2.5.2 residentKey

- **Required:** 身份验证器必须创建一个常驻密钥（如果不可能，则操作应该失败）。
- **Preferred:** 身份验证器应尝试创建一个常驻密钥（如果不可能，则应创建一个非常驻密钥）。
- **Discouraged:** 身份验证器必须创建一个非常驻密钥（如果不可能，则操作应失败）。

> **常驻密钥（也称为可发现凭据）：** 常驻密钥存储在身份验证器上，并在身份验证期间检索。以此方式，客户端可以发现可能的密钥列表，这就是 [Conditional UI](https://www.corbado.com/blog/user-transition-passkeys-&gt; conditional-ui) 需要常驻密钥的原因。
> **非常驻密钥（也称为不可发现凭据）：** 如果是非常驻密钥，凭据 ID 将存储在服务器上，并在身份验证期间提供。凭据 ID 是一种[不透明标识符](https://www.w3.org/TR/webauthn-3/#sctn-credential-id)，其内部结构因实施而异——身份验证器可以直接存储私钥、使用加密的密钥包装，或从内部机密中派生密钥。确切的机制因身份验证器实现而异。

#### 2.5.3 userVerification

- **Required:** 操作应验证用户。
- **Preferred:** 操作应验证用户，但也可以不验证继续进行（标准选项）。
- **Discouraged:** 操作不应验证用户。

> **警告：**如果设置为“**Preferred”**，则用户或其设备可以跳过身份验证过程中的用户验证（阅读此[文章](https://web.dev/articles/passkey-form-autofill)了解更多信息）。

## 3. Conditional UI (条件 UI)

当用户已向依赖方注册常驻密钥时，**Conditional UI**（通行密钥自动填充）会在选择下拉列表中向用户显示可用的通行密钥。它提高了通行密钥的可用性，但需要额外的开发工作，并且并非在所有操作系统/浏览器组合中可用。

### 3.1 带有 Conditional UI 的登录流

![WebAuthn 中具有 Conditional UI 的登录流程的过程流](https://www.corbado.com/website-assets/65fae71a75d50f6ba14041d9_cs_3_1_conditional_ui_flow_min_394a12f521.png)_像常规登录一样，Conditional UI 也使用对象 PublicKeyCredentialRequestOptions 和断言_

### 3.2 设备兼容性

Conditional UI 尚未在所有操作系统和浏览器组合上可用。以下是当前浏览器覆盖范围的概述（2024 年 3 月）：

![显示 Conditional UI 在操作系统/浏览器组合上可用性的表格](https://www.corbado.com/website-assets/65fae8d6321e72be11445913_cs_3_2_conditional_ui_compatibility_min_d5767f13b3.png)

如需最新概述，请访问[此网站](https://caniuse.com/mdn-api_publickeycredential_isconditionalmediationavailable_static)。

### 3.3 代码示例

#### 3.3.1 Conditional UI 方法

Conditional UI 方法的完整、最简代码如下所示：

```html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Conditional UI</title>
    </head>
    <body>
        <input type="text" id="username" autocomplete="username webauthn" />

        <script>
            async function passkeyLogin() {
                try {
                    // 从 WebAuthn 服务器检索请求选项（包括质询）
                    let options = await WebAuthnClient.getPublicKeyRequestOptions();

                    const credential = await navigator.credentials.get({
                        publicKey: options.publicKeyCredentialRequestOptions,
                        mediation: "conditional",
                    });

                    const userData = await WebAuthnClient.sendSignedChallenge(credential);
                    window.location.href = "/logged-in";
                } catch (error) {
                    console.log(error);
                }
            }

            passkeyLogin();
        </script>
    </body>
</html>
```

#### 3.3.2 浏览器兼容性检查

Conditional UI 仅适用于**常驻密钥**/可发现凭据<br/>
建议提供**不同的服务器端点**以启动 Conditional UI 登录。<br/>
客户端需要满足多项要求：

- 浏览器需要支持 Conditional UI（请参阅 [3.2 设备兼容性](#device-compatibility)）。
- 必须启用 JavaScript 并且网页必须提供 HTML 输入字段。
- 超时参数应被忽略。

为避免错误，服务器首先应使用以下函数测试客户端可用性：
在真实流量中，检测和生命周期问题通常表现为 `NotAllowedError` 或 `AbortError`。使用本指南以区分预期错误与意外错误，包括原生凭据管理器通行密钥错误。

```javascript
// 来源：https://developer.mozilla.org/en-US/docs/Web/API/PublicKeyCredential/isConditionalMediationAvailable#examples

// `window.PublicKeyCredential` 可用意味着 WebAuthn 可用。
if (window.PublicKeyCredential && PublicKeyCredential.isConditionalMediationAvailable) {
    // 检查条件中介是否可用。
    const isCMA = await PublicKeyCredential.isConditionalMediationAvailable();
    if (isCMA) {
        // 调用 WebAuthn 身份验证启动端点

        let options = await WebAuthnClient.getPublicKeyRequestOptions();

        const credential = await navigator.credentials.get({
            publicKey: options.publicKeyCredentialRequestOptions,
            mediation: "conditional",
        });
        /*
    ...
    */
    }
}
```

#### 3.3.3 输入字段中的自动完成令牌

输入字段应接收 HTML 自动填充令牌，该令牌向客户端发出将通行密钥填充到进行中请求的信号。除了通行密钥外，自动填充令牌还可以与现有令牌搭配使用，例如用户名和密码：

- autocomplete="username webauthn": 除了显示通行密钥外，这还会建议用户名自动填充。
- autocomplete="current-password webauthn": 除了显示通行密钥外，这还会进一步提示密码自动填充。

```html
<label for="name">用户名：</label>
<input type="text" name="name" autocomplete="username webauthn" />
<label for="password">密码：</label>
<input type="password" name="password" autocomplete="current-password webauthn" />
```

## 4. WebAuthn 服务器

### 4.1 数据库架构

WebAuthn 服务器没有强制或标准化的数据库架构。但是，此示例数据库架构可用于存储所需信息并提供 WebAuthn 服务器的所有功能：

![WebAuthn 服务器的示例数据库架构，包含有关“用户”和“凭据”的数据](https://www.corbado.com/website-assets/65fc7cda64ff7798cdbb2dfc_cs_4_1_database_schema_min_8ac4536120.png)_加粗属性对于最小可行实现是强制性的，而其他属性仅对于可选但有用的功能才是必需的。_

#### 4.1.1 身份验证相关数据

- **凭据 ID:** 这是在通行密钥注册期间由身份验证器生成的唯一 ID。应使用它来查找与通行密钥关联的实际用户帐户。此外，应该比较 userHandle（来自 user_id）以验证用于身份验证的帐户。请勿使用 user.name 属性进行比较，因为它可能会随时间改变。
- **用户 ID (user_id):** 由依赖方指定的用于表示其系统中用户帐户的唯一 ID。它作为 userHandle 在 assertion 对象中返回。

#### 4.1.2 用于显示和选择通行密钥的元数据：

- **用户显示名称 (user.displayName):** 用户友好的、可读的名称，通常是用户的全名。它会显示给用户，但不在身份验证期间使用。

- **用户名 (user.name):** 唯一的、可读的名称，通常是电子邮件地址或用户名。它可以显示给用户，但在身份验证期间不使用。

### 4.2 依赖方 ID (rpId)

**依赖方 ID (rpID)** 是存储在通行密钥中的域，确保通行密钥仅适用于正确的域（浏览器 URL，请参阅本文了解原生应用程序）。在身份验证期间，将针对浏览器 URL 检查 rpID，并且仅在以下两种情况下允许：

1. URL 与 rpId 完全匹配，或者
2. URL 是与 rpId 匹配的子域，并且父域不在公共后缀列表中

以下是（不）允许的组合示例：

![允许和不允许的依赖方 ID 组合示例](https://www.corbado.com/website-assets/65fc7d099bf36909d7f40495_cs_4_2_rpid_examples_min_55f5750e90.png)

## 5. 帮助网站和工具

以下是用于实施通行密钥的有用工具和网站列表。

- [**Passkeys Debugger:**](https://www.passkeys-debugger.io/) 用于将 WebAuthn 响应调试为 JSON 并测试不同选项下 WebAuthn 操作的工具。
- [**通行密钥词汇表:**](https://www.corbado.com/glossary) 通行密钥相关术语和概念解释
- [**WebAuthn 规范:**](https://www.w3.org/TR/webauthn-2/) 这是官方的 WebAuthn 规范。
- [**Chrome Device Log:**](http://chrome://device-log) 用于显示设备 WebAuthn 操作日志的工具（仅在 Chrome 上通过 `chrome://device-log` 获得）

对于技术实现以外优化通行密钥用户体验的策略，请参阅我们关于通行密钥创建最佳实践和通行密钥登录最佳实践的指南。

如果只想用几行代码在任何应用程序中实施通行密钥，也可以使用 [Corbado Complete](https://docs.corbado.com/start)（用于新应用程序）或 [Corbado Connect](https://www.corbado.com/enterprise)（用于现有应用程序）

## 常见问题

### 如何在我的 Web 应用程序中实现用于通行密钥自动填充的 Conditional UI？

Conditional UI 需要在启动身份验证前通过 `PublicKeyCredential.isConditionalMediationAvailable()` 检查浏览器支持。输入字段必须包含 `autocomplete="username webauthn"` HTML 令牌，并且用户必须已注册常驻密钥（可发现凭据）。建议使用单独的服务器端点来处理 Conditional UI 登录流程。

### 要支持 WebAuthn 身份验证，我需要在数据库中存储的最少数据是什么？

至少需要存储身份验证器在注册期间生成的凭据 ID，以及用户 ID (user_id)，它作为 userHandle 在断言对象中返回。使用凭据 ID 查找关联的用户帐户，并比较 userHandle 以验证身份验证。避免使用 user.name 进行比较，因为它可能会随时间变化。

### WebAuthn 中常驻密钥和非常驻密钥的区别是什么？

常驻密钥（可发现凭据）存储在身份验证器本身，并在身份验证期间检索，这是 Conditional UI 正常工作所必需的。非常驻密钥将凭据 ID 存储在服务器上，并在身份验证期间将其发送给身份验证器。authenticatorSelection 中的 residentKey 字段使用“required”、“preferred”或“discouraged”等值来控制此行为。

### userVerification 是如何工作的，将其设置为 preferred 的风险是什么？

userVerification 字段控制身份验证器是否必须在登录期间验证用户，接受值为“required”、“preferred”（默认）或“discouraged”。当设置为“preferred”时，用户或其设备可以在身份验证过程中完全跳过验证，这可能会降低安全性。将其设置为“required”可确保始终在身份验证完成之前进行验证。
