跳到主要内容
版本:7.0.2

通行密钥

DeepSeek V3 中英对照 Passkeys

Spring Security 支持 passkeys。Passkeys 是一种比密码更安全的身份验证方法,它基于 WebAuthn 构建。

为了使用通行密钥进行身份验证,用户必须先注册新凭证。凭证注册完成后,即可通过验证认证断言来进行身份验证。

必需依赖项

要开始使用,请将 webauthn4j-core 依赖项添加到您的项目中。

备注

此说明假设您正在使用 Spring Boot 或 Spring Security 的 BOM(物料清单)来管理 Spring Security 的版本,具体方法详见获取 Spring Security

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>0.29.7.RELEASE</version>
</dependency>

配置

以下配置启用了通行密钥认证。它提供了在 /webauthn/register 路径下注册新凭证的方法,以及一个允许使用通行密钥进行认证的默认登录页面。

@Bean
SecurityFilterChain filterChain(HttpSecurity http) {
// ...
http
// ...
.formLogin(withDefaults())
.webAuthn((webAuthn) -> webAuthn
.rpId("example.com")
.allowedOrigins("https://example.com")
// optional properties
.creationOptionsRepository(new CustomPublicKeyCredentialCreationOptionsRepository())
.messageConverter(new CustomHttpMessageConverter())
);
return http.build();
}

@Bean
UserDetailsService userDetailsService() {
UserDetails userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();

return new InMemoryUserDetailsManager(userDetails);
}

JDBC 与自定义持久化

WebAuthn 通过 PublicKeyCredentialUserEntityRepositoryUserCredentialRepository 实现持久化。默认使用内存持久化,但通过 JdbcPublicKeyCredentialUserEntityRepositoryJdbcUserCredentialRepository 支持 JDBC 持久化。要配置基于 JDBC 的持久化,请将这两个仓库作为 Bean 暴露:

@Bean
JdbcPublicKeyCredentialUserEntityRepository jdbcPublicKeyCredentialRepository(JdbcOperations jdbc) {
return new JdbcPublicKeyCredentialUserEntityRepository(jdbc);
}

@Bean
JdbcUserCredentialRepository jdbcUserCredentialRepository(JdbcOperations jdbc) {
return new JdbcUserCredentialRepository(jdbc);
}

如果 JDBC 无法满足您的需求,您可以自行实现相关接口,并通过将其暴露为 Bean 的方式使用它们,具体方法可参考上述示例。

自定义 PublicKeyCredentialCreationOptionsRepository

PublicKeyCredentialCreationOptionsRepository 用于在请求之间持久化 PublicKeyCredentialCreationOptions。默认情况下,它会被持久化到 HttpSession 中,但有时用户可能需要自定义此行为。这可以通过设置 配置 部分展示的可选属性 creationOptionsRepository 来实现,或者通过暴露一个 PublicKeyCredentialCreationOptionsRepository Bean 来实现:

@Bean
CustomPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
return new CustomPublicKeyCredentialCreationOptionsRepository();
}

注册新凭证

要使用通行密钥,用户首先必须注册新凭证

注册新凭证包含两个步骤:

  1. 请求注册选项

  2. 注册凭证

请求注册选项

注册新凭证的第一步是请求注册选项。在 Spring Security 中,通常使用 JavaScript 来请求注册选项,其形式如下:

备注

Spring Security 提供了一个默认的注册页面,可作为如何注册凭证的参考。

POST /webauthn/register/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

上述请求将获取当前已认证用户的注册选项。由于挑战值会被持久化(状态已更改)以便在注册时进行比对,该请求必须为 POST 方法并包含 CSRF 令牌。

{
"rp": {
"name": "SimpleWebAuthn Example",
"id": "example.localhost"
},
"user": {
"name": "user@example.localhost",
"id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
"displayName": "user@example.localhost"
},
"challenge": "q7lCdd3SVQxdC-v8pnRAGEn1B2M-t7ZECWPwCAmhWvc",
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -8
},
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
}
],
"timeout": 300000,
"excludeCredentials": [],
"authenticatorSelection": {
"residentKey": "required",
"userVerification": "preferred"
},
"attestation": "none",
"extensions": {
"credProps": true
}
}

注册凭证

获取注册选项后,将使用这些选项创建待注册的凭证。要注册新凭证,应用程序应在对二进制值(例如 user.idchallengeexcludeCredentials[].id)进行 base64url 解码后,将选项传递给 navigator.credentials.create

然后,可以将返回值作为JSON请求发送到服务器。下面是一个注册请求的示例:

POST /webauthn/register
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

{
"publicKey": { // <1>
"credential": {
"id": "dYF7EGnRFFIXkpXi9XU2wg",
"rawId": "dYF7EGnRFFIXkpXi9XU2wg",
"response": {
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YViUy9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNhdAAAAALraVWanqkAfvZZFYZpVEg0AEHWBexBp0RRSF5KV4vV1NsKlAQIDJiABIVggQjmrekPGzyqtoKK9HPUH-8Z2FLpoqkklFpFPQVICQ3IiWCD6I9Jvmor685fOZOyGXqUd87tXfvJk8rxj9OhuZvUALA",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiSl9RTi10SFJYRWVKYjlNcUNrWmFPLUdOVmlibXpGVGVWMk43Z0ptQUdrQSIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
"transports": [
"internal",
"hybrid"
]
},
"type": "public-key",
"clientExtensionResults": {},
"authenticatorAttachment": "platform"
},
"label": "1password" // <2>
}
}
  • 调用 navigator.credentials.create 时,二进制值采用 base64url 编码的结果。

  • 用户选择与此凭证关联的标签,用于帮助用户区分凭证。

HTTP/1.1 200 OK

{
"success": true
}

验证认证断言

注册新凭证后,可以对通行密钥进行验证(身份验证)。

验证凭证由两个步骤组成:

  1. 请求验证选项

  2. 验证凭证

请求验证选项

验证凭据的第一步是请求验证选项。在 Spring Security 中,通常使用 JavaScript 来请求验证选项,其形式如下:

备注

Spring Security 提供了一个默认的登录页面,可作为如何验证凭据的参考。

POST /webauthn/authenticate/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

上述请求将获取验证选项。由于挑战值会被持久化(状态已更改)以便在认证时进行比对,该请求必须为 POST 方法并包含 CSRF 令牌。

响应将包含获取凭据的选项,其中包含二进制值,例如经过 base64url 编码的 challenge

{
"challenge": "cQfdGrj9zDg3zNBkOH3WPL954FTOShVy0-CoNgSewNM",
"timeout": 300000,
"rpId": "example.localhost",
"allowCredentials": [],
"userVerification": "preferred",
"extensions": {}
}

验证凭证

获取验证选项后,将使用这些选项来获取凭证。为获取凭证,应用程序应在对二进制值(例如 challenge)进行 base64url 解码后,将选项传递给 navigator.credentials.get

navigator.credentials.get 的返回值随后可以作为 JSON 请求发送到服务器。二进制值(例如 rawIdresponse.*)必须进行 base64url 编码。下面是一个认证请求的示例:

POST /login/webauthn
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721

{
"id": "dYF7EGnRFFIXkpXi9XU2wg",
"rawId": "dYF7EGnRFFIXkpXi9XU2wg",
"response": {
"authenticatorData": "y9GqwTRaMpzVDbXq1dyEAXVOxrou08k22ggRC45MKNgdAAAAAA",
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiRFVsRzRDbU9naWhKMG1vdXZFcE9HdUk0ZVJ6MGRRWmxUQmFtbjdHQ1FTNCIsIm9yaWdpbiI6Imh0dHBzOi8vZXhhbXBsZS5sb2NhbGhvc3Q6ODQ0MyIsImNyb3NzT3JpZ2luIjpmYWxzZX0",
"signature": "MEYCIQCW2BcUkRCAXDmGxwMi78jknenZ7_amWrUJEYoTkweldAIhAMD0EMp1rw2GfwhdrsFIeDsL7tfOXVPwOtfqJntjAo4z",
"userHandle": "Q3_0Xd64_HW0BlKRAJnVagJTpLKLgARCj8zjugpRnVo"
},
"clientExtensionResults": {},
"authenticatorAttachment": "platform"
}
HTTP/1.1 200 OK

{
"redirectUrl": "/", // <1>
"authenticated": true // <2>
}
  • 重定向目标 URL

  • 指示用户已通过身份验证

HTTP/1.1 401 OK