パスキー
Spring Security はパスキー (英語) のサポートを提供します。パスキーはパスワードよりも安全な認証方法で、WebAuthn [W3C] (英語) を使用して構築されます。
パスキーを使用して認証するには、ユーザーはまず新しい資格情報を登録するする必要があります。資格情報が登録されたら、認証アサーションを検証することで認証に使用できます。
必要な依存関係
開始するには、プロジェクトに webauthn4j-core
依存関係を追加します。
これは、Spring Security の入手に従って、Spring Security のバージョンを Spring Boot または Spring Security の BOM を使用して管理していることを前提としています。 |
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>com.webauthn4j</groupId>
<artifactId>webauthn4j-core</artifactId>
<version>0.28.3.RELEASE</version>
</dependency>
depenendencies {
implementation "org.springframework.security:spring-security-web"
implementation "com.webauthn4j:webauthn4j-core:0.28.3.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")
);
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)
}
新しい資格情報を登録する
パスキーを使用するには、ユーザーはまず新しい資格情報を登録する [W3C] (英語) を実行する必要があります。
新しい資格情報の登録は、次の 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.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 | base64url でエンコードされたバイナリ値を使用して navigator.credentials.create を呼び出した結果。 |
2 | ユーザーが資格情報を区別できるように、この資格情報に関連付けるために選択したラベル。 |
HTTP/1.1 200 OK
{
"success": true
}
認証アサーションの検証
新しい資格情報を登録する以降はパスキーを検証 [W3C] (英語) (認証) できます。
資格情報の検証は、次の 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 リクエストとしてサーバーに送信できます。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