Skip to content
Published on

SiteMinder から Keycloak へ — レガシー SSO マイグレーション戦略と実戦ロードマップ

Authors

はじめに — 先送りしてきた宿題に取り組む時

前回の記事で SiteMinder のアーキテクチャを解剖しました。今回はその次の問い、**「では、どうやって抜け出すのか」**を扱います。

2026年現在、この宿題をもう先送りできない理由は明確になりました。Keycloak 26.6 は FAPI 2.0 Final、ログインフローへの passkeys 統合、zero-downtime ローリングアップデート、Workflows による realm 管理自動化まで備え、エンタープライズ要件を十分に受け止められる水準になりました。標準側でも OAuth 2.1 draftRFC 9700 によってベストプラクティスが整理されています。一方 SiteMinder 側は、12.8.04 の EOS(2023年12月)が示すようにバージョンのライフサイクル管理自体が常時リスクであり、Broadcom 買収後のライセンスポリシーの変化はコスト予測を困難にしました。

本記事はコンサル資料ではなく実務ロードマップを目指します。インベントリの作り方、SiteMinder オブジェクトの Keycloak へのマッピング、共存アーキテクチャの構成、パスワードマイグレーション、ロールバック計画まで — 実際のプロジェクトでぶつかる順番に整理します。

マイグレーションの動機 — なぜ離れるのか

組織ごとに事情は異なりますが、動機はおおむね3つに収斂します。

1. EOS とサポートリスク

  • 12.8.04 は 2023年12月に EOS を迎えました。旧バージョンに留まる環境はセキュリティパッチの空白に晒されます。
  • 12.9 へのアップグレード自体も小さなプロジェクトではないため、「どうせ大工事をするなら標準スタックへ」という判断が自然に生まれます。

2. ライセンスコスト

  • ユーザー数ベースのライセンスと保守費用は、年間で数千万円規模になるケースが珍しくありません。
  • Keycloak はオープンソース(Apache 2.0)であり、商用サポートが必要なら Red Hat build of Keycloak のような選択肢があります。コスト構造が「ライセンス」から「運用能力」へ移動します。

3. 標準の不在とエコシステムの断絶

  • SMSESSION は非標準のため、モバイルアプリ、SPA、API、AI エージェント(non-human identity)のようなモダンワークロードとの統合が困難です。
  • OIDC/SAML/SCIM のような標準中心の Keycloak は、新規サービスとの統合コストが構造的に低い。
  • 採用市場で SiteMinder 運用人材を探すのは年々難しくなる一方、Keycloak 経験者は増えています。

フェーズ 0: インベントリ — すべては分類から始まる

マイグレーション失敗の最も多い原因は技術ではなくインベントリの不備です。XPSExport でポリシーストアをダンプし、アプリを以下のカテゴリに分類することが出発点です。

# ポリシーストア全体のエクスポート
XPSExport sm-policy-export.xml -xb -vT

# realm/response 数などの一次統計を抽出 (例)
grep -c "<Realm" sm-policy-export.xml
grep -o 'SM_[A-Z_]*' sm-policy-export.xml | sort | uniq -c | sort -rn

アプリ分類体系

分類特徴移行難易度移行方法
A. 標準プロトコルアプリすでに SAML/OIDC で連携(フェデレーションパートナー)IdP エンドポイントを Keycloak に差し替えるだけ
B. ヘッダーベースアプリ(修正可能)SM_USER ヘッダー依存、ソース修正可能OIDC クライアントとして書き換え、またはプロキシ
C. ヘッダーベースアプリ(修正不可)ベンダー廃業、ソースなし、変更凍結プロキシパターンでヘッダー再現(無修正)
D. エージェント API 依存アプリSiteMinder SDK/Agent API を直接呼び出し最高個別分析のうえ再設計が必要

分類とあわせて、アプリごとに次のメタデータを収集します。

  • 依存するヘッダーの一覧(SM_USER だけか? SM_USERGROUPS、カスタム response ヘッダーまでか?)
  • セッションタイムアウト要件(realm の idle/max 値)
  • 認証スキーム(Forms? 証明書? Kerberos? step-up の有無)
  • ユーザーストア(どの LDAP/AD を参照しているか、ユーザー属性の依存関係)
  • トラフィック規模と業務重要度(移行順序の決定に使用)

このインベントリは Excel ではなく、バージョン管理される構造化データ(YAML/JSON)として作ることを推奨します。移行進捗ダッシュボードのデータソースになります。

# app-inventory.yaml (例)
- appId: hr-payroll
  category: C            # ヘッダーベース、修正不可
  headers: [SM_USER, SM_USERGROUPS, X-Custom-Empno]
  authScheme: forms
  sessionIdle: 30m
  owner: hr-it-team
  criticality: high
  wave: 3                # 移行ウェーブの割り当て

ビッグバン vs 段階的移行

項目ビッグバン段階的(推奨)
移行期間短い(理論上)長い(1〜3年)
リスク全社同時障害の可能性ウェーブ単位に局所化
ロールバック事実上不可能ウェーブ単位で可能
共存インフラ不要SAML ブリッジ/プロキシが必要
適した環境アプリ30個未満の小規模金融機関/大企業の数百アプリ

アプリが数十を超える環境でのビッグバンはギャンブルです。以下の内容は段階的移行を前提とします。段階的移行の中心課題は、移行期間中に2つの IdP が共存しながらも、ユーザーは一度だけログインすればよい状態を作ることです。

共存(Coexistence)アーキテクチャ — 2つの世界の橋

SAML ブローカリング: Keycloak と SiteMinder の相互連携

両システムとも SAML 2.0 を話せることが橋になります。方向は2つあります。

方向 1: Keycloak が IdP、SiteMinder が SP(目標状態へ向かう推奨方向)

新規/移行済みのアプリは Keycloak で直接ログインし、まだ SiteMinder 配下にあるレガシーアプリにアクセスすると、SiteMinder が SAML SP として Keycloak に認証を委譲します。認証の信頼できる情報源が先に Keycloak へ移るため、passkeys のようなモダンな認証手段を全社に先行適用できます。

 ユーザー
   |
   | 1. ログイン (passkey/OTP/パスワード)
   v
+------------------+    SAML assertion    +--------------------+
|     Keycloak     | -------------------> |     SiteMinder     |
|   (新 IdP)       |   (SM が SAML SP)    |  (レガシー WebSSO)  |
+--------+---------+                      +---------+----------+
         |                                          |
         | OIDC/SAML                                | SMSESSION + SM_USER
         v                                          v
+------------------+                      +--------------------+
| 移行済みアプリ群    |                      | 未移行レガシーアプリ群 |
| (OIDC クライアント)|                      | (ヘッダーベース)      |
+------------------+                      +--------------------+

方向 2: SiteMinder が IdP、Keycloak がブローカー(移行初期に有用)

既存のログイン体験に手を付けず、新規アプリから Keycloak に接続したい場合、Keycloak の Identity Brokering 機能で SiteMinder を外部 IdP として登録します。ユーザーは引き続き SiteMinder のログイン画面を見ますが、新規アプリは標準 OIDC で開発されます。

Keycloak で SAML Identity Provider を登録する設定例は次のとおりです。

# kcadm で SiteMinder を SAML IdP として登録
/opt/keycloak/bin/kcadm.sh create identity-provider/instances -r enterprise \
  -s alias=siteminder-idp \
  -s providerId=saml \
  -s enabled=true \
  -s 'config.entityId=https://keycloak.example.com/realms/enterprise' \
  -s 'config.singleSignOnServiceUrl=https://sso.example.com/affwebservices/public/saml2sso' \
  -s 'config.nameIDPolicyFormat=urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified' \
  -s 'config.postBindingResponse=true' \
  -s 'config.wantAssertionsSigned=true'

セッションブリッジングの現実

2つの IdP のセッションは別物です。SAML ブローカリングは「再認証なしに相手側のセッションを作ってくれる」ものであり、セッションを1つに統合するものではありません。したがって、次を設計段階で決める必要があります。

  • セッション寿命の整合: Keycloak の SSO Session Idle/Max を SiteMinder realm の idle/max と意図的に合わせるか、短い方を基準に統一します。
  • ログアウト伝搬: 片方でログアウトしてももう片方のセッションが生き残る問題。SiteMinder のログアウト URL と Keycloak の front-channel logout をチェーンするログアウトオーケストレーションページを設けるのが現実的な解決策です。
  • このテーマは次の記事(ハイブリッド共存パターン)でさらに深く扱います。

ヘッダーベースアプリのためのプロキシパターン — oauth2-proxy

分類 C(修正不可のヘッダーベースアプリ)への解決策です。Web Agent がやっていたこと — 認証確認後のユーザーヘッダー注入 — を oauth2-proxy と nginx で再現します。

 ユーザー --> nginx (auth_request) --> oauth2-proxy --> Keycloak (OIDC)
              |                          |
              | 認証 OK + ヘッダー受信      |
              v                          |
        レガシーアプリ (SM_USER ヘッダーを   |
        そのまま受け取る — コード無修正) <----+

oauth2-proxy 設定の核心は、Keycloak の OIDC クレームを SiteMinder のヘッダー名へ変換する部分です。

# oauth2-proxy.yaml (alpha config)
upstreamConfig:
  upstreams:
    - id: hr-payroll
      path: /
      uri: http://hr-payroll.internal:8080
providers:
  - id: keycloak-oidc
    provider: keycloak-oidc
    clientID: legacy-bridge-proxy
    clientSecretFile: /secrets/client-secret
    oidcConfig:
      issuerURL: https://keycloak.example.com/realms/enterprise
      emailClaim: email
      audienceClaims: [aud]
injectRequestHeaders:
  - name: SM_USER                 # レガシーアプリが期待するヘッダー名そのまま
    values:
      - claim: preferred_username
  - name: SM_USERGROUPS
    values:
      - claim: groups             # Keycloak の mapper でキャレット(^)結合形式を再現
  - name: X-Custom-Empno
    values:
      - claim: employeeNumber

nginx 側は auth_request パターンで接続します。

server {
  listen 443 ssl;
  server_name hr-payroll.example.com;

  location /oauth2/ {
    proxy_pass http://oauth2-proxy:4180;
    proxy_set_header X-Forwarded-Uri $request_uri;
  }

  location / {
    auth_request /oauth2/auth;
    error_page 401 = /oauth2/start?rd=$scheme://$host$request_uri;

    # oauth2-proxy が検証後に返したヘッダーをアップストリームへ伝達
    auth_request_set $sm_user $upstream_http_sm_user;
    auth_request_set $sm_groups $upstream_http_sm_usergroups;
    proxy_set_header SM_USER $sm_user;
    proxy_set_header SM_USERGROUPS $sm_groups;

    # 外部から偽造されて入ってきた同名ヘッダーは上で無条件に上書きされる
    proxy_pass http://hr-payroll.internal:8080;
  }
}

セキュリティ注意: 前回の記事で強調した header injection の原則がそのまま適用されます。レガシーアプリは必ずこのプロキシ経由でのみアクセス可能になるようネットワークを分離し、プロキシはクライアントが送ってきた SM_USER 系ヘッダーを上書きしなければなりません。

グループクレームを SiteMinder 形式(キャレット区切り)に合わせる必要があれば、Keycloak の Script Mapper またはカスタム protocol mapper で処理します。

// Keycloak カスタム ProtocolMapper の核心ロジック (キャレット区切りグループ文字列)
@Override
protected void setClaim(IDToken token, ProtocolMapperModel model,
                        UserSessionModel userSession, KeycloakSession session,
                        ClientSessionContext ctx) {
    String joined = userSession.getUser().getGroupsStream()
            .map(GroupModel::getName)
            .collect(Collectors.joining("^"));
    token.getOtherClaims().put("smUserGroups", joined);
}

ポリシーマイグレーション — オブジェクトマッピング表

SiteMinder のオブジェクトモデルを Keycloak の概念へ移すときの基準マッピングです。

SiteMinder オブジェクトKeycloak 対応概念備考
Policy DomainRealm またはクライアントのグルーピング組織単位が大きければ realm 分離を検討
Realm (URL 領域+スキーム)Client + 要求認証フローURL 保護はアプリ/プロキシの責務へ移動
Rule (リソース+アクション)Authorization Services の resource/scopeまたはアプリレベルの認可へ移動
Policy (rule+ユーザー)Role/Group マッピング、AuthZ policyLDAP グループは group-ldap-mapper で同期
Response (ヘッダー注入)Protocol Mapper (クレーム注入)ヘッダーインベントリがマッピングの入力
Auth SchemeAuthentication Flowstep-up は ACR/LoA 条件付きフローで
Protection LevelACR (Authentication Context Class)step-up authentication を再現
User Store (AD/LDAP)User Federation (LDAP provider)初期は既存 LDAP をそのまま接続
Session (idle/max)SSO Session Idle / Maxrealm 単位での統一の必要性を検討
OnAuthAccept イベントルールEvent Listener SPI / Required Actionカスタム SPI 開発の領域

重要な観点の転換が一つあります。SiteMinder は URL 中心(この URL に誰がアクセスできるか)であり、Keycloak はクライアント/トークン中心(このアプリにどんなクレームとロールを発行するか)です。URL 単位の細かい認可がどうしても必要なら Keycloak Authorization Services またはゲートウェイレベル(例: プロキシのパス別グループ検査)へ移しますが、可能ならアプリ内部の認可に下ろす方が長期的に健全です。

LDAP User Federation は共存初期の鍵となる仕組みです。両システムが同じユーザーストアを参照すれば、アカウント同期の問題が消えます。

# 既存の AD/LDAP を Keycloak の User Federation として接続
/opt/keycloak/bin/kcadm.sh create components -r enterprise \
  -s name=corp-ldap \
  -s providerId=ldap \
  -s providerType=org.keycloak.storage.UserStorageProvider \
  -s 'config.connectionUrl=["ldaps://ad.example.com:636"]' \
  -s 'config.usersDn=["ou=people,dc=example,dc=com"]' \
  -s 'config.bindDn=["cn=svc-keycloak,ou=svc,dc=example,dc=com"]' \
  -s 'config.editMode=["READ_ONLY"]' \
  -s 'config.syncRegistrations=["false"]'

ユーザー/パスワードマイグレーション — 漸進的キャプチャ

同じ LDAP を共有するならパスワード問題はありません。しかし SiteMinder が独自のユーザーストアを使っている場合や、この機会に LDAP 自体を廃止したい場合、パスワードハッシュを移せない状況(独自ハッシュ、ポリシー上 export 禁止)が生じます。このとき使うのが**漸進的パスワードキャプチャ(gradual password capture)**です。

  1. ユーザープロファイル(ID、メール、属性)は SCIM(RFC 7644)またはバッチで先行移行し、パスワードは空のままにします。
  2. Keycloak にカスタム User Storage SPI を配置します。ユーザーが初めて Keycloak でログインすると、SPI が入力されたパスワードでレガシーストア(LDAP bind または SiteMinder 認証 API)に検証を委譲します。
  3. 検証に成功したら、そのパスワードを Keycloak 自身のクレデンシャル(argon2 ハッシュ)として保存し、以降はローカル検証に切り替えます。
  4. 数か月後、キャプチャ率が十分に上がったら(例: 95パーセント)、残りのユーザーにパスワード再設定を案内してレガシーストアを廃止します。
// User Storage SPI の核心: 初回ログイン時のレガシー検証 + キャプチャ
@Override
public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) {
    String rawPassword = input.getChallengeResponse();
    boolean legacyOk = legacyAuthClient.validate(user.getUsername(), rawPassword);
    if (legacyOk) {
        // キャプチャ: Keycloak ローカルクレデンシャルとして保存 → 次回からローカル検証
        user.credentialManager().updateCredential(
            UserCredentialModel.password(rawPassword));
        log.infof("Password captured for user=%s", user.getUsername());
    }
    return legacyOk;
}

このパターンは、ユーザーに「パスワード再設定の大混乱」を起こさずにストアを乗り換える実証済みの方法です。ただし、キャプチャ期間中はレガシー認証経路の可用性が Keycloak ログインの可用性に影響するため、サーキットブレーカーと監視を付ける必要があります。

ウェーブ単位のロールアウトとロールバック計画

ウェーブ設計

Wave 0 (パイロット)   : 社内 IT チーム用の低リスクアプリ 2〜3個。全パターンを検証。
Wave 1 (低リスク)     : トラフィックが少なく業務影響の低いアプリ 10〜20個。
Wave 2〜N (本移行)    : 部門/ドメイン単位の束。ウェーブあたり 2〜6週間。
Wave Final (中枢部)   : 基幹業務アプリ。十分な共存運用実績の後で。
最後: SiteMinder デコミッション (ポリシーアーカイブ、ライセンス終了)

ウェーブごとに同じチェックリストを繰り返します。

  1. 事前: 対象アプリのヘッダー/セッション要件の再確認、Keycloak クライアント/プロキシの構成、ステージングでの検証
  2. 切り替え: DNS/ロードバランサーのルーティングを新経路(プロキシまたは OIDC 直接)へ変更。可能ならカナリア(トラフィックの一部のみ新経路)を適用
  3. 観察: 認証成功率、ログイン所要時間、ヘルプデスクチケット数を最低 1〜2週間モニタリング
  4. 確定またはロールバック

ロールバック計画

ロールバックが容易な構造を意図的に作るべきです。

  • 切り替えの実体をルーティング変更に限定すれば(アプリも SiteMinder ポリシーも削除しない)、ロールバックはルーティングを戻す一回の操作です。
  • SiteMinder のポリシーはウェーブ確定後 N 週間が経過するまで削除せず、無効化状態で保存します。
  • ロールバックのトリガーを事前に数値で合意します。例:「認証成功率がベースライン比 2 ポイント以上の低下を 30 分継続したら自動ロールバック」。
# ルーティング切替/復旧をスクリプト化しておく (例: nginx upstream の差し替え)
./switch-route.sh hr-payroll --to keycloak-proxy   # 切り替え
./switch-route.sh hr-payroll --to siteminder       # ロールバック (1分以内)

検証シナリオ — ウェーブごとに回すテスト

最低限、次のシナリオを自動化しておくことを推奨します。

番号シナリオ確認ポイント
T1未認証ユーザーが保護 URL にアクセスログインリダイレクト、元の URL への復帰
T2ログイン後、同じウェーブの別アプリにアクセスSSO(再ログインなし)
T3レガシーアプリと新アプリの行き来ブリッジ経由の SSO、二重ログインなし
T4ヘッダー値の整合性SM_USER などがレガシーと同一形式
T5セッション idle 失効失効後の再認証要求、無限リダイレクトなし
T6ログアウト両側のセッションが終了するか
T7ヘッダー偽造の試行外部注入の SM_USER が無視/上書きされるか
T8グループベースの認可権限のないグループの 403 確認
T9step-up 認証高保護アプリ進入時の再認証/2FA 要求
T10障害注入Keycloak 1ノード停止時もログイン継続

T4(ヘッダー整合性)は特に重要です。レガシー環境と新プロキシ環境へ同じリクエストを送り、ヘッダーダンプを diff するハーネスを作っておけば、ウェーブごとに再利用できます。

# ヘッダー整合性 diff ハーネス (概念例)
curl -s -b "$LEGACY_COOKIE" https://legacy.example.com/echo-headers > legacy.txt
curl -s -b "$KC_COOKIE" https://bridge.example.com/echo-headers > new.txt
diff <(grep '^SM_' legacy.txt | sort) <(grep '^SM_' new.txt | sort)

Identity Orchestration ツールという選択肢

すべてを自前で構築する代わりに、移行期の複雑さを吸収してくれる商用レイヤーもあります。Strata Maverics のような identity orchestration 製品は、SiteMinder とモダン IdP の間に抽象化レイヤーを置き、アプリごとにどの IdP を使うかをポリシーで切り替え、セッション変換とヘッダー注入を代行します。

  • 長所: 共存インフラ(プロキシ、ブリッジ、ログアウト同期)を自前で開発する必要がない。マルチ IdP 環境(M&A など)で特に強力。
  • 短所: もう一つのベンダー依存とライセンスコスト。「SiteMinder から脱出したらオーケストレーションベンダーにロックインされた」とならないよう、オーケストレーションレイヤーは期間限定のツールとして計画するのが良いでしょう。

組織のエンジニアリング能力が十分でアプリのパターンが単純(大半がヘッダーベース)なら oauth2-proxy ベースの自前構築が、パターンが複雑でスケジュールが厳しいならオーケストレーションツールの導入が合理的です。

よくある落とし穴集

  • ヘッダーインベントリの漏れ: SM_USER だけ移してカスタム response ヘッダー(X-Custom-Empno の類)を見落とし、切り替え当日にアプリが真っ白な画面を出す事例が最も多い。
  • グループ区切り文字の不一致: キャレット(^)区切り文字列を期待するパーサーに JSON 配列やカンマ区切り値を渡し、権限がすべて消える問題。
  • セッション寿命不一致によるリダイレクトループ: プロキシセッションは生きているのに Keycloak SSO セッションが先に死ぬと、構成によっては無限リダイレクトが発生します。トークンリフレッシュとセッション寿命を階層的に整合させる必要があります。
  • エンコーディング: 非 ASCII のユーザー属性(日本語名など)のエンコーディングがレガシー(RFC 2047)と新環境(UTF-8)で異なり、下流のパーサーが壊れる問題。T4 ハーネスに非 ASCII ケースを必ず含めてください。
  • 時系列なしの切り替え判断: 切り替え直後ではなく、月末バッチや四半期決算のような周期業務で発火する問題があります。ウェーブ確定前の観察期間に業務サイクルを含めてください。
  • 「最後の5パーセント」の放置: 一部のアプリは最後まで移行されません。デコミッション目標日を定め、その日以降の残存アプリの維持コストを当該アプリのオーナー部門に課金するガバナンスが実際に効果的です。
  • サービスアカウントとバッチジョブの漏れ: 人間のユーザーだけをインベントリに載せ、ヘッダー認証を模倣していたバッチ/監視ボットを見落とす事例。non-human identity は最初から別トラックに分離し、client credentials フローへ移してください。
  • ヘルプデスクの準備不足: ウェーブ切り替え日の問い合わせ急増は予測可能なイベントです。切り替え前にヘルプデスクへ新しいログイン画面のキャプチャと FAQ を配布するだけで、対応時間が大幅に減ります。
  • 監査ログ保存の漏れ: 規制業種は認証監査ログを数年間保存する必要があります。SiteMinder のデコミッション前にレガシー監査ログの保存方針(アーカイブ場所、照会手順)を確定しないと、監査シーズンに困ることになります。

おわりに

SiteMinder から Keycloak へのマイグレーションは IdP の入れ替えプロジェクトではなく、アプリケーションポートフォリオのモダナイゼーションプログラムです。成功の8割はインベントリと分類、そしてロールバック可能なウェーブ設計で決まります。技術的には、SAML ブローカリングと oauth2-proxy のヘッダー変換という2つの橋を堅牢に架ければ、残りは反復可能な運用プロセスの問題です。

次の記事では、この移行期が数年続くという現実を受け入れ、レガシー WebSSO とモダン IAM の共存アーキテクチャパターン — プロトコルブリッジ、リバースプロキシのヘッダー注入、identity orchestration、strangler fig — を、銀行イントラネット 200 アプリの仮想事例とともに深く扱います。

参考資料