此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Security 6.5.3! |
通行密钥
所需的依赖项
要开始使用,请添加webauthn4j-core
依赖项目。
这假设您正在使用 Spring Boot 或 Spring Security 的 BOM 管理 Spring Security 的版本,如获取 Spring Security 中所述。 |
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>0.29.5.RELEASE</version>
</dependency>
depenendencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:0.29.5.RELEASE"
}
配置
以下配置启用密钥身份验证。
它提供了一种在/webauthn/register
以及允许使用通行密钥进行身份验证的默认登录页面。
-
Java
-
Kotlin
@Bean
SecurityFilterChain filterChain(HttpSecurity http) {
// ...
http
// ...
.formLogin(withDefaults())
.webAuthn((webAuthn) -> webAuthn
.rpName("Spring Security Relying Party")
.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);
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
// ...
http {
webAuthn {
rpName = "Spring Security Relying Party"
rpId = "example.com"
allowedOrigins = setOf("https://example.com")
// optional properties
creationOptionsRepository = CustomPublicKeyCredentialCreationOptionsRepository()
messageConverter = CustomHttpMessageConverter()
}
}
}
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
JDBC 和自定义持久性
WebAuthn 使用PublicKeyCredentialUserEntityRepository
和UserCredentialRepository
.
默认值是使用内存持久性,但 JDBC 持久性支持JdbcPublicKeyCredentialUserEntityRepository
和JdbcUserCredentialRepository
.
要配置基于JDBC的持久性,请将存储库公开为Bean:
-
Java
-
Kotlin
@Bean
JdbcPublicKeyCredentialUserEntityRepository jdbcPublicKeyCredentialRepository(JdbcOperations jdbc) {
return new JdbcPublicKeyCredentialUserEntityRepository(jdbc);
}
@Bean
JdbcUserCredentialRepository jdbcUserCredentialRepository(JdbcOperations jdbc) {
return new JdbcUserCredentialRepository(jdbc);
}
@Bean
fun jdbcPublicKeyCredentialRepository(jdbc: JdbcOperations): JdbcPublicKeyCredentialUserEntityRepository {
return JdbcPublicKeyCredentialUserEntityRepository(jdbc)
}
@Bean
fun jdbcUserCredentialRepository(jdbc: JdbcOperations): JdbcUserCredentialRepository {
return JdbcUserCredentialRepository(jdbc)
}
如果 JDBC 不能满足您的需求,您可以创建自己的接口实现,并通过将它们公开为 Bean 来使用它们,类似于上面的示例。
自定义 PublicKeyCredentialCreationOptionsRepository
这PublicKeyCredentialCreationOptionsRepository
用于持久化PublicKeyCredentialCreationOptions
请求之间。
默认设置是将其持久化HttpSession
,但有时用户可能需要自定义此行为。
这可以通过设置可选属性来完成creationOptionsRepository
在配置中演示或通过公开PublicKeyCredentialCreationOptionsRepository
豆:
-
Java
-
Kotlin
@Bean
CustomPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
return new CustomPublicKeyCredentialCreationOptionsRepository();
}
@Bean
open fun creationOptionsRepository(): CustomPublicKeyCredentialCreationOptionsRepository {
return CustomPublicKeyCredentialCreationOptionsRepository()
}
注册新凭证
为了使用密钥,用户必须首先注册新凭证。
注册新凭证由两个步骤组成:
-
请求注册选项
-
注册凭证
请求注册选项
注册新凭证的第一步是请求注册选项。 在 Spring Security 中,对注册选项的请求通常使用 JavaScript 完成,如下所示:
Spring Security 提供了一个默认注册页面,可用作有关如何注册凭据的参考。 |
POST /webauthn/register/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
上述请求将获取当前经过身份验证的用户的注册选项。 由于在注册时会保留质询(状态已更改)以进行比较,因此请求必须是 POST 并包含 CSRF Tokens。
{
"rp": {
"name": "SimpleWebAuthn Example",
"id": "example.localhost"
},
"user": {
"name": "[email protected]",
"id": "oWJtkJ6vJ_m5b84LB4_K7QKTCTEwLIjCh4tFMCGHO4w",
"displayName": "[email protected]"
},
"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
}
}
注册凭证
获取注册选项后,它们将用于创建注册的凭据。
若要注册新凭据,应用程序应将选项传递给navigator.credentials.create
在 base64url 解码二进制值后,例如user.id
,challenge
和excludeCredentials[].id
.
然后,返回的值可以作为 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)
}
}
1 | 调用的结果navigator.credentials.create 使用二进制值 base64url 编码。 |
2 | 用户选择与此凭据关联的标签,以帮助用户区分凭据。 |
HTTP/1.1 200 OK
{
"success": true
}
验证身份验证断言
验证凭据由两个步骤组成:
-
请求验证选项
-
验证凭据
请求验证选项
验证凭据的第一步是请求验证选项。 在 Spring Security 中,对验证选项的请求通常是使用 JavaScript 完成的,如下所示:
Spring Security 提供了一个默认登录页面,可用作有关如何验证凭据的参考。 |
POST /webauthn/authenticate/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
上述请求将获取验证选项。 由于质询在身份验证时被持久化(状态已更改)以进行比较,因此请求必须是 POST 并包含 CSRF Tokens。
响应将包含用于获取具有二进制值(例如challenge
base64url 编码。
{
"challenge": "cQfdGrj9zDg3zNBkOH3WPL954FTOShVy0-CoNgSewNM",
"timeout": 300000,
"rpId": "example.localhost",
"allowCredentials": [],
"userVerification": "preferred",
"extensions": {}
}
验证凭据
获取验证选项后,它们用于获取凭据。
若要获取凭据,应用程序应将选项传递给navigator.credentials.get
在 base64url 解码二进制值后,例如challenge
.
的返回值navigator.credentials.get
然后可以作为 JSON 请求发送到服务器。
二进制值,例如rawId
和response.*
必须是 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)
}
1 | 要重定向到的 URL |
2 | 指示用户已通过身份验证 |
HTTP/1.1 401 OK