セッション管理
セッション管理は、ユーザーのログイン状態を維持し、シングルサインオン(SSO)やログアウト連携を実現するための仕組みです。
セッションとは
セッション(Session) とは、ユーザーの認証状態を一定期間保持する仕組みです。
セッションの役割
- 認証状態の保持: ログイン後、毎回パスワード入力せ ずにサービス利用可能
- シングルサインオン(SSO): 一度のログインで複数のアプリケーションにアクセス
- ログアウト連携: 一箇所でログアウトすると関連するすべてのアプリからログアウト
セッションとトークンの違い
| 項目 | セッション | トークン |
|---|---|---|
| 用途 | ブラウザとIdP間の状態管理 | クライアントとリソースサーバー間の認可 |
| 保存場所 | Cookie(ブラウザ側)+ Redis/DB(サーバー側) | クライアントアプリケーション |
| 有効期限 | セッションタイムアウト(通常30分〜数時間) | トークン有効期限(アクセストークン: 分〜時間) |
| 識別対象 | ユーザーのブラウザ | クライアントアプリケーションのリクエスト |
idp-serverのセッション管理
なぜSpring Sessionを使わないか
idp-serverは、OIDC Session Managementの要件を満たすため、独自のセッション管理を実装しています。
| Spring Session | OIDC Session Management |
|---|---|
| 1ブラウザ = 1セッション | 1 OPセッション : N クライアントセッション |
| HttpSessionの分散化が目的 | sid, sub での複合検索が必要 |
| RP(クライアント)側での利用を想定 | OP(IdP)側でのセッション管理 |
この要件の違いから、Keycloakなどと同様に独自実装を採用しています。
セッションの階層構造
idp-serverでは、2層のセッション構造を採用しています。
┌──────────────────────────────────────────── ─────────────────────┐
│ Browser Session │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ OPSession │ │
│ │ - ブラウザとOP間のセッション(SSO用) │ │
│ │ - sub, authTime, acr, amr を保持 │ │
│ │ - 複数のClientSessionを持つ │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ClientSession │ │ ClientSession │ │ ClientSession │ │
│ │ アプリA │ │ アプリB │ │ アプリC │ │
│ │ sid: xxx │ │ sid: yyy │ │ sid: zzz │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
OPSession(OPセッション)
ブラウザとIdP(OP)間のセッションです。ユーザーがログインすると作成されます。
| 属性 | 説明 |
|---|---|
| id | セッションID(UUID) |
| sub | ユーザー識別子 |
| authTime | 認証時刻 |
| acr | 認証コンテキストクラス(認証強度) |
| amr | 認証方式(password, otp, fido等) |
| expiresAt | 有効期限 |
ClientSession(クライアントセッション)
OPSessionと特定のアプリケーション(RP)間のセッションです。認可が完了すると作成されます。
| 属性 | 説明 |
|---|---|
| sid | セッションID(ID Tokenのsidクレームに含まれる) |
| opSessionId | 親となるOPSessionのID |
| clientId | アプリケーションのClient ID |
| scopes | 認可されたスコープ |
| nonce | 認可リクエストのnonce |
Cookieの役割
idp-serverは、セッション管理に複数のCookieを使用します。
セッション識別Cookie
| Cookie名 | 内容 | HttpOnly | 目的 |
|---|---|---|---|
IDP_IDENTITY | OPSessionのID | Yes | SSO識別用(サーバー側で使用) |
IDP_SESSION | SHA256(opSessionId) | No | Session Management iframe用 |
- IDP_IDENTITY: サーバー側でセッションを識 別するためのCookie(HttpOnlyでセキュア)
- IDP_SESSION: OIDC Session Managementのiframeでセッション状態を確認するためのCookie
認可フロー保護Cookie
| Cookie名 | 内容 | 目的 |
|---|---|---|
AUTH_SESSION | 認可セッションID | 認可フロー乗っ取り攻撃の防止 |
認可リクエストからトークン取得までの一連のフローを、同一ブラウザセッション内でのみ有効にします。
テナント分離
Cookieのパスでテナントを分離できます。
Browser Cookie Storage:
├── /tenant-a/
│ ├── IDP_IDENTITY = "session-id-for-tenant-a"
│ └── IDP_SESSION = "hash-a..."
│
└── /tenant-b/
├── IDP_IDENTITY = "session-id-for-tenant-b"
└── IDP_SESSION = "hash-b..."
これにより、同一ブラウザで複数テナントに独立してログインできます。
シングルサインオン(SSO)
OPSessionにより、一度のログインで複数のアプリケーションにアクセスできます。
1. ユーザーがアプリAにアクセス
└─ ログインしてOPSession作成 → ClientSession(A)作成
2. ユーザーがアプリBにアクセス
└─ OPSessionが有効なので再認証不要 → ClientSession(B)作成
3. ユーザーがアプリCにアクセス
└─ OPSessionが有効なので再認証不要 → ClientSession(C)作成
max_ageパラメータ
アプリケーションは認可リクエストでmax_ageパラメータを指定することで、最後の認証からの経過時間を制限できます。
max_age=300 → 最後の認証から5分以上経過していたら再認証を要求
prompt=loginパラメータ
prompt=loginを指定すると、既存のセッションを無視して再認証を要求できます。
ログアウト
idp-serverは、OIDC仕様に準拠した複数のログアウト方式をサポートしています。
RP-Initiated Logout
アプリケーション(RP)からログアウトを開始する方式です。
1. ユーザーがアプリでログアウトボタンをクリック
2. アプリがIdPの/logoutエンドポイントにリダイレクト
3. IdPがセッションを終了
4. IdPが指定されたURLにリダイレクト
Back-Channel Logout
IdPからアプリケーションにサーバー間通信でログアウトを通知する方式です。
1. ユーザーがIdPまたは他のアプリでログアウト
2. IdPが各アプリのbackchannel_logout_uriにLogout Tokenを送信
3. 各アプリがローカルセッションを終了
特徴:
- ユーザーのブラウザを経由しない(サーバー間通信)
- 信頼性が高い
- アプリがオフラインでも後で処理可能
Front-Channel Logout
IdP からアプリケーションにブラウザ経由でログアウトを通知する方式です。
1. ユーザーがIdPでログアウト
2. IdPがログアウト確認ページを表示
3. ページ内のiframeで各アプリのfrontchannel_logout_uriを読み込み
4. 各アプリがローカルセッションを終了
特徴:
- ユーザーのブラウザを経由
- Cookie削除などブラウザ側の処理が可能
- ブラウザの制限(3rdパーティCookieブロック等)の影響を受ける
セッションストレージ
idp-serverは、Redisをセッションストレージとして使用します。
なぜRedisか
| 要件 | Redisの利点 |
|---|---|
| 高速アクセス | インメモリDB |
| TTL(有効期限) | ネイティブサポート |
| 複合検索 | セカンダリインデックス |
| 分散環境 | クラスタ構成対応 |
インデックス構造
効率的な検索のため、以下のインデックスを作成します。
# OPSession
op_session:{tenantId}:{opSessionId} # メインデータ
idx:tenant:{tenantId}:sub:{sub} # ユーザー別検索
# ClientSession
client_session:{tenantId}:{sid} # メインデータ
idx:tenant:{tenantId}:op_session:{opSessionId} # OPSession別検索
idx:tenant:{tenantId}:sub:{sub} # ユーザー別検索
idx:tenant:{tenantId}:client:{clientId}:sub:{sub} # クライアント×ユーザー検索