認証実装ガイド
このドキュメントの目的
認証インタラクション(パスワード、SMS、FIDO2等の認証実行)の実装を理解することが目標です。
所要時間
⏱️ 約30分
前提知識
認証インタラクションとは
Authorization Code Flowの中で、ユーザー本人確認を行う処理。
Authorization Request
↓
[ログイン画面表示]
↓
【認証インタラクション】← このドキュメントの対象
├─ パスワード認証
├─ SMS OTP認証
├─ FIDO2認証
└─ など
↓
Authorization Code発行
アーキテクチャ 全体像
30秒で理解する全体像
HTTPリクエスト
↓
Controller (AuthenticationV1Api) - HTTP処理
↓
EntryService (OAuthFlowEntryService.interact()) - オーケストレーション
├─ Tenant取得
├─ AuthorizationRequest取得
├─ OAuthSession取得
├─ AuthenticationTransaction取得(認証状態)
├ ─ AuthenticationInteractor選択(Plugin)
├─ 認証実行(interact)
├─ AuthenticationTransaction更新
├─ OAuthSession更新(成功時)
├─ ロック処理(失敗回数超過時)
└─ イベント発行
↓
Core層 (AuthenticationInteractor) - 認証ロジック
├─ PasswordAuthenticationInteractor
├─ SmsAuthenticationInteractor
├─ EmailAuthenticationInteractor
├─ WebAuthnAuthenticationInteractor
├─ FidoUafAuthenticationInteractor
└─ DeviceAuthenticationInteractor
↓
Repository - ユーザー検証・認証状態保存
主要クラスの責務
| クラス | 層 | 役割 | 実装 |
|---|---|---|---|
| AuthenticationV1Api | Controller | HTTPエンドポイント | AuthenticationV1Api.java |
| OAuthFlowEntryService | UseCase | トランザクション・オーケストレーション | OAuthFlowEntryService.java:164-214 |
| AuthenticationInteractor | Core | 認証ロジック(Plugin拡張可能) | libs/idp-server-authentication-interactors/ |
| AuthenticationTransaction | Core | 認証状態管理(完了した認証・残り認証) | Core Domain |
| OAuthSession | Core | セッション管理(認証成功後の情報保持) | Core Domain |
データフロー
┌─────────────────────────────────────────────────────┐
│ 1. AuthenticationTransaction(認証開始時作成) │
├─────────────────────────────────────────────────────┤
│ - identifier: auth-req-12345 │
│ - authenticationPolicy: { success_conditions: ... }│
│ - interactionResults: {} ← 空 │
│ - request.user: null │
└──────────────────────────────────────── ─────────────┘
↓ パスワード認証実行
┌─────────────────────────────────────────────────────┐
│ 2. AuthenticationTransaction(更新後) │
├─────────────────────────────────────────────────────┤
│ - interactionResults: { │
│ "password": { │
│ successCount: 1, failureCount: 0, │
│ attemptCount: 1, method: "password" │
│ } │
│ } │
│ - request.user: User(sub=user-12345) │
│ - isSuccess(): false ← まだSMS 必要 │
└─────────────────────────────────────────────────────┘
↓ SMS認証実行
┌─────────────────────────────────────────────────────┐
│ 3. AuthenticationTransaction(認証完了) │
├─────────────────────────────────────────────────────┤
│ - interactionResults: { │
│ "password": {successCount: 1, ...}, │
│ "sms": {successCount: 1, ...} │
│ } │
│ - request.user: User(sub=user-12345) │
│ - isSuccess(): true ← 認証完了! │
└─────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────┐
│ 4. OAuthSession(認証情報をセッションに保存) │
├─────────────────────────────────────────────────────┤
│ - user: User(sub=user-12345) │
│ - authentication: Authentication( │
│ methods=["password", "sms"], │
│ acr="urn:mace:incommon:iap:silver" │
│ ) │
│ - sessionKey: tenant-12345:client-abc │
│ → Authorization Code発 行で使用 │
└─────────────────────────────────────────────────────┘
AuthenticationTransaction(認証状態管理)
役割
認証フロー全体の状態を管理。以下の情報を保持:
- identifier: AuthenticationTransaction識別子
- authorizationIdentifier: 紐づくAuthorization Request ID
- request: 認証リクエスト(ユーザー情報含む)
- authenticationPolicy: 認証ポリシー(success/failure/lock条件)
- interactionResults: 各認証方式の試行結果(試行回数 ・成功回数・失敗回数)
- attributes: カスタム属性
主要メソッド
実装: AuthenticationTransaction.java
public class AuthenticationTransaction {
AuthenticationTransactionIdentifier identifier;
AuthorizationIdentifier authorizationIdentifier;
AuthenticationRequest request;
AuthenticationPolicy authenticationPolicy;
AuthenticationInteractionResults interactionResults; // 認証結果の集合
AuthenticationTransactionAttributes attributes;
/**
* 認証完了チェック(success_conditions評価)
*/
public boolean isSuccess() {
if (hasAuthenticationPolicy()) {
AuthenticationResultConditionConfig successConditions =
authenticationPolicy.successConditions();
return MfaConditionEvaluator.isSuccessSatisfied(
successConditions, interactionResults);
}
return interactionResults.containsAnySuccess();
}
/**
* 認証失敗チェック(failure_conditions評価)
*/
public boolean isFailure() {
if (hasAuthenticationPolicy()) {
AuthenticationResultConditionConfig failureConditions =
authenticationPolicy.failureConditions();
return MfaConditionEvaluator.isFailureSatisfied(
failureConditions, interactionResults);
}
return interactionResults.containsDenyInteraction();
}
/**
* アカウントロックチェック(lock_conditions評価)
*/
public boolean isLocked() {
if (hasAuthenticationPolicy()) {
AuthenticationResultConditionConfig lockConditions =
authenticationPolicy.lockConditions();
return MfaConditionEvaluator.isLockedSatisfied(
lockConditions, interactionResults);
}
return false;
}
/**
* 処理完了チェック
*/
public boolean isComplete() {
return isSuccess() || isFailure() || isLocked();
}
/**
* 認証結果で更新
*/
public AuthenticationTransaction updateWith(
AuthenticationInteractionRequestResult result) {
Map<String, AuthenticationInteractionResult> resultMap = interactionResults.toMap();
// ユーザー情報を更新
AuthenticationRequest updatedRequest = updateWithUser(result);
// 既存の認証結果がある場合は更新、なければ追加
if (interactionResults.contains(result.interactionTypeName())) {
AuthenticationInteractionResult foundResult =
interactionResults.get(result.interactionTypeName());
AuthenticationInteractionResult updatedInteraction = foundResult.updateWith(result);
resultMap.put(result.interactionTypeName(), updatedInteraction);
} else {
// 新しい認証結果を追加
String operationType = result.operationType().name();
String method = result.method();
int successCount = result.isSuccess() ? 1 : 0;
int failureCount = result.isSuccess() ? 0 : 1;
LocalDateTime interactionTime = SystemDateTime.now();
AuthenticationInteractionResult newResult =
new AuthenticationInteractionResult(
operationType, method, 1, successCount, failureCount, interactionTime);
resultMap.put(result.interactionTypeName(), newResult);
}
AuthenticationInteractionResults updatedResults =
new AuthenticationInteractionResults(resultMap);
return new AuthenticationTransaction(
identifier,
authorizationIdentifier,
updatedRequest,
authenticationPolicy,
updatedResults,
attributes);
}
/**
* 最終的なAuthentication生成(Authorization Code発行時に使用)
*/
public Authentication authentication() {
if (!isSuccess()) {
return new Authentication();
}
LocalDateTime time = interactionResults.authenticationTime();
List<String> methods = interactionResults.authenticationMethods();
String acr = AcrResolver.resolve(authenticationPolicy.acrMappingRules(), methods);
return new Authentication()
.setTime(time)
.addMethods(methods)
.addAcr(acr);
}
}
重要な実装ポイント:
interactionResultsで管理: 各認証方式の試行回数・成功回数・失敗回数を記録- 条件評価による判定:
MfaConditionEvaluatorがsuccess_conditions等を評価 - イミュータブル:
updateWith()は新しいインスタンスを返す - 最終的な
Authentication生成:authentication()メソッドがACR等を含むオブジェクトを生成
状態遷移
[初期状態]
interactionResults: {}
isSuccess(): false
isComplete(): false
↓ パスワード認証成功
[パスワード認証完了]
interactionResults: {
"password": {successCount: 1, failureCount: 0, attemptCount: 1}
}
isSuccess(): false(2要素認証ポリシーの場合)
isComplete(): false ← まだ完了していない
↓ SMS認証成功
[認証完了]
interactionResults: {
"password": {successCount: 1, failureCount: 0, attemptCount: 1},
"sms": {successCount: 1, failureCount: 0, attemptCount: 1}
}
isSuccess(): true ← success_conditions満たした
isComplete(): true
↓
Authorization Code発行可能
判定メソッドの使い分け:
isSuccess(): 認証成功(success_conditionsを満たす)isFailure(): 認証失敗(failure_conditionsを満たす)isLocked(): アカウントロック(lock_conditionsを満たす)isComplete(): 処理完了(success/failure/lockedのいずれか)
エンドポイント
POST /{tenant-id}/v1/authentications/{authorization-request-id}/{interaction-type}
{
"username": "user@example.com",
"password": "secret"
}