認証インタラクター
目的
AuthenticationInteractorは、各種認証方式(Password、WebAuthn、SMS、Email、Device等)の認証フローを実装するためのインターフェースです。
このガイドは、新しい認証方式を追加する開発者向けの標準手順・設計指針を示します。
前提知識
このガイドを読む前に:
- impl-05-authentication-policy.md - 認証ポリシー
AuthenticationInteractor インターフェース
情報源: AuthenticationInteractor.java:23-40
public interface AuthenticationInteractor {
// ✅ インタラクションタイプ
AuthenticationInteractionType type();
// ✅ オペレーションタイプ(デフォルト: AUTHENTICATION)
default OperationType operationType() {
return OperationType.AUTHENTICATION;
}
// ✅ 認証方式名(AMR値)
String method();
// ✅ 認証インタラクション実行
AuthenticationInteractionRequestResult interact(
Tenant tenant,
AuthenticationTransaction transaction,
AuthenticationInteractionType type,
AuthenticationInteractionRequest request,
RequestAttributes requestAttributes,
UserQueryRepository userQueryRepository);
}
重要ポイント:
- ✅ operationType()はdefaultメソッド: Challenge専用Interactorのみオーバーライド
- ✅ method(): RFC 8176準拠のAMR値を返す
- ✅ interact(): Tenant第一引数(マルチテナント分離)
実装パターン: Password認証
情報源: PasswordAuthenticationInteractor.java:30-80
完全な実装例
public class PasswordAuthenticationInteractor implements AuthenticationInteractor {
PasswordVerificationDelegation passwordVerificationDelegation;
LoggerWrapper log = LoggerWrapper.getLogger(PasswordAuthenticationInteractor.class);
public PasswordAuthenticationInteractor(
PasswordVerificationDelegation passwordVerificationDelegation) {
this.passwordVerificationDelegation = passwordVerificationDelegation;
}
@Override
public AuthenticationInteractionType type() {
return StandardAuthenticationInteraction.PASSWORD_AUTHENTICATION.toType();
}
@Override
public String method() {
return StandardAuthenticationMethod.PASSWORD.type(); // "pwd"
}
@Override
public AuthenticationInteractionRequestResult interact(
Tenant tenant,
AuthenticationTransaction transaction,
AuthenticationInteractionType type,
AuthenticationInteractionRequest request,
RequestAttributes requestAttributes,
UserQueryRepository userQueryRepository) {
log.debug("PasswordAuthenticationInteractor called");
// 1. リクエストから値を取得
String username = request.optValueAsString("username", "");
String password = request.optValueAsString("password", "");
String providerId = request.optValueAsString("provider_id", "idp-server");
// 2. ユーザー検索
User user = userQueryRepository.findByEmail(tenant, username, providerId);
// 3. パスワード検証
if (!passwordVerificationDelegation.verify(password, user.hashedPassword())) {
// 認証失敗
Map<String, Object> response = new HashMap<>();
response.put("error", "invalid_request");
response.put("error_description", "user is not found or invalid password");
return new AuthenticationInteractionRequestResult(
AuthenticationInteractionStatus.CLIENT_ERROR,
type,
operationType(),
method(),
user,
response,
DefaultSecurityEventType.password_failure);
}
// 4. 認証成功
Map<String, Object> response = new HashMap<>();
response.put("status", "authenticated");
return new AuthenticationInteractionRequestResult(
AuthenticationInteractionStatus.SUCCESS,
type,
operationType(),
method(),
user,
response,
DefaultSecurityEventType.password_success);
}
}
実装パターン: Challenge-Response型(WebAuthn)
Challenge生成
public class WebAuthnAuthenticationChallengeInteractor implements AuthenticationInteractor {
@Override
public OperationType operationType() {
return OperationType.CHALLENGE; // ← Challengeオーバーライド
}
@Override
public String method() {
return StandardAuthenticationMethod.FIDO.type(); // "fido"
}
@Override
public AuthenticationInteractionRequestResult interact(...) {
// WebAuthnチャレンジ生成
WebAuthnChallenge challenge = WebAuthnChallenge.generate();
return new AuthenticationInteractionRequestResult(
AuthenticationInteractionStatus.SUCCESS,
type,
OperationType.CHALLENGE,
method(),
user,
Map.of("challenge", challenge.value(),
"rpId", tenant.domain(),
"timeout", 60000),
null); // Challengeはイベント発行しない
}
}
認証検証
public class WebAuthnAuthenticationInteractor implements AuthenticationInteractor {
WebAuthnExecutors executors; // webauthn4jアダプター
@Override
public AuthenticationInteractionRequestResult interact(...) {
// WebAuthn Assertion検証
WebAuthnExecutor executor = executors.get(WebAuthnExecutorType.AUTHENTICATION);
WebAuthnVerificationResult result = executor.verify(
tenant,
request.optValueAsString("credential_id", ""),
request.optValueAsString("authenticator_data", ""),
request.optValueAsString("client_data_json", ""),
request.optValueAsString("signature", ""));
if (result.isSuccess()) {
return new AuthenticationInteractionRequestResult(
AuthenticationInteractionStatus.SUCCESS,
type,
operationType(),
method(),
result.user(),
Map.of("status", "authenticated"),
DefaultSecurityEventType.webauthn_authentication_success);
}
}
}
新しい認証方式の追加手順(7ステップ)
Step 1: Interactor実装
package org.idp.server.authentication.interactors.custom;
public class CustomAuthenticationInteractor implements AuthenticationInteractor {
@Override
public AuthenticationInteractionType type() {
return new AuthenticationInteractionType("custom_auth");
}
@Override
public String method() {
return "custom"; // カスタムAMR値
}
@Override
public AuthenticationInteractionRequestResult interact(
Tenant tenant,
AuthenticationTransaction transaction,
AuthenticationInteractionType type,
AuthenticationInteractionRequest request,
RequestAttributes requestAttributes,
UserQueryRepository userQueryRepository) {
// カスタム認証ロジック実装
// 1. リクエストから値取得
// 2. 認証検証
// 3. 成功/失敗結果を返却
}
}
Step 2: Factory実装
public class CustomAuthenticationInteractorFactory implements AuthenticationInteractorFactory {
@Override
public AuthenticationInteractor create(AuthenticationDependencyContainer container) {
// 依存を取得
CustomAuthenticationService service = container.resolve(CustomAuthenticationService.class);
// Interactor生成
return new CustomAuthenticationInteractor(service);
}
}
Step 3: Plugin登録
ファイル: src/main/resources/META-INF/services/org.idp.server.core.openid.authentication.plugin.AuthenticationInteractorFactory
org.idp.server.authentication.interactors.custom.CustomAuthenticationInteractorFactory