Passkeys

Spring Security 為通行金鑰提供支援。通行金鑰是一種比密碼更安全的認證方法,並基於WebAuthn構建。

為了使用通行金鑰進行身份驗證,使用者必須首先註冊新憑據。憑據註冊後,可以透過驗證身份驗證斷言來使用它進行身份驗證。

所需依賴項

首先,將 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.7.RELEASE</version>
</dependency>
depenendencies {
    implementation "org.springframework.security:spring-security-web"
    implementation "com.webauthn4j:webauthn4j-core:0.29.7.RELEASE"
}

配置

以下配置啟用通行金鑰身份驗證。它提供了一種在 /webauthn/register 註冊新憑據的方式,以及一個允許使用通行金鑰進行身份驗證的預設登入頁面。

  • Java

  • Kotlin

@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);
}
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
	// ...
	http {
		webAuthn {
			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 使用 PublicKeyCredentialUserEntityRepositoryUserCredentialRepository 執行持久化。預設使用記憶體持久化,但透過 JdbcPublicKeyCredentialUserEntityRepositoryJdbcUserCredentialRepository 支援 JDBC 持久化。要配置基於 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 Bean 來完成。

  • Java

  • Kotlin

@Bean
CustomPublicKeyCredentialCreationOptionsRepository creationOptionsRepository() {
	return new CustomPublicKeyCredentialCreationOptionsRepository();
}
@Bean
open fun creationOptionsRepository(): CustomPublicKeyCredentialCreationOptionsRepository {
	return 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": "[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.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)
  }
}
1 呼叫 navigator.credentials.create 並對二進位制值進行 base64url 編碼的結果。
2 使用者選擇的與此憑據關聯的標籤,以幫助使用者區分憑據。
註冊成功響應示例
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 令牌。

響應將包含用於獲取憑據的選項,其中包含諸如 challenge 等 base64url 編碼的二進位制值。

驗證選項響應示例
{
  "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)
}
1 要重定向到的 URL
2 指示使用者已透過身份驗證
身份驗證失敗響應示例
HTTP/1.1 401 OK
© . This site is unofficial and not affiliated with VMware.