Passkeys
所需依賴
要開始使用,請將 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.28.6.RELEASE</version>
</dependency>
depenendencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:0.28.6.RELEASE"
}
配置
以下配置啟用 passkey 認證。它提供了在 /webauthn/register
註冊新憑證以及一個預設登入頁面,該頁面允許使用 passkey 進行認證。
-
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")
);
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")
}
}
}
@Bean
open fun userDetailsService(): UserDetailsService {
val userDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(userDetails)
}
註冊新憑證
為了使用 passkey,使用者必須首先註冊新憑證。
註冊新憑證包含兩個步驟
-
請求註冊選項
-
註冊憑證
請求註冊選項
註冊新憑證的第一步是請求註冊選項。在 Spring Security 中,通常使用 JavaScript 請求註冊選項,如下所示
Spring Security 提供了一個預設註冊頁面,可以作為如何註冊憑證的參考。 |
POST /webauthn/register/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
上述請求將獲取當前已認證使用者的註冊選項。由於挑戰(challenge)被持久化(狀態改變)以便在註冊時進行比較,因此請求必須是 POST 幷包含 CSRF 令牌。
{
"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
}
}
註冊憑證
獲取註冊選項後,將使用它們建立要註冊的憑證。要註冊新憑證,應用程式應在對二進位制值(如 user.id
、challenge
和 excludeCredentials[].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)
}
}
1 | 呼叫 navigator.credentials.create 並在其中對二進位制值進行 base64url 編碼後的結果。 |
2 | 使用者選擇的與此憑證關聯的標籤,用於幫助使用者區分憑證。 |
HTTP/1.1 200 OK
{
"success": true
}
驗證認證斷言
註冊新憑證後,可以驗證(認證)passkey。
驗證憑證包含兩個步驟
-
請求驗證選項
-
驗證憑證
請求驗證選項
驗證憑證的第一步是請求驗證選項。在 Spring Security 中,通常使用 JavaScript 請求驗證選項,如下所示
Spring Security 提供了一個預設登入頁面,可以作為如何驗證憑證的參考。 |
POST /webauthn/authenticate/options
X-CSRF-TOKEN: 4bfd1575-3ad1-4d21-96c7-4ef2d9f86721
上述請求將獲取驗證選項。由於挑戰(challenge)被持久化(狀態改變)以便在認證時進行比較,因此請求必須是 POST 幷包含 CSRF 令牌。
響應將包含獲取憑證的選項,其中二進位制值(如 challenge
)已進行 base64url 編碼。
{
"challenge": "cQfdGrj9zDg3zNBkOH3WPL954FTOShVy0-CoNgSewNM",
"timeout": 300000,
"rpId": "example.localhost",
"allowCredentials": [],
"userVerification": "preferred",
"extensions": {}
}
驗證憑證
獲取驗證選項後,將使用它們獲取憑證。要獲取憑證,應用程式應在對二進位制值(如 challenge
)進行 base64url 解碼後,將選項傳遞給 navigator.credentials.get
。
然後可以將 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