はじめに
OIDC が新規構築のデフォルトとなった 2026 年でも、エンタープライズ B2B SSO の現場では依然として SAML 2.0 が共通語です。顧客企業の Entra ID、Okta、あるいは自社構築の IdP と連携せよという要件を受け取ると、半分以上の確率で SAML メタデータの XML ファイルが送られてきます。2005 年に標準化されたプロトコルが 20 年以上も現役である理由は単純です。**すでに敷設された信頼関係の慣性**と、**ブラウザさえあれば動くというシンプルな前提**です。
問題は、SAML が「設定すれば動くブラックボックス」として扱われ、障害が起きると誰も中身を知らないという点です。本記事では SAML 2.0 の核心である Assertion、Protocol(AuthnRequest/Response)、Binding、Metadata を実際の XML レベルで解剖し、XML Signature Wrapping のような攻撃と防御、さらにクロックスキューのような運用課題まで扱います。
SAML 2.0 の 4 層構造
SAML スペックは 4 つのレイヤーで構成されます。この区分を知っていると、スペック文書がはるかに読みやすくなります。
+-----------------------------------------------------------+
| Profiles : レイヤーを組み合わせた利用シナリオ |
| (Web Browser SSO Profile、Single Logout など) |
+-----------------------------------------------------------+
| Bindings : メッセージを運ぶ転送方法 |
| (HTTP-Redirect, HTTP-POST, Artifact, SOAP) |
+-----------------------------------------------------------+
| Protocols : 要求/応答メッセージの形式 |
| (AuthnRequest, Response, LogoutRequest など) |
+-----------------------------------------------------------+
| Assertions : アイデンティティ情報の本体 |
| (AuthnStatement, AttributeStatement など) |
+-----------------------------------------------------------+
- **Assertion**: 「このユーザーは誰で、いつどのように認証され、どんな属性を持つか」という陳述の XML 文書。
- **Protocol**: Assertion を要求し応答するメッセージの規格。
- **Binding**: そのメッセージを HTTP の上にどう載せるかのルール。
- **Profile**: 上の 3 つを束ねて「Web ブラウザ SSO」という完結したシナリオにしたもの。
私たちが普段「SAML 連携」と呼ぶものは、ほぼ常に **Web Browser SSO Profile** です。
Assertion の解剖 — アイデンティティ陳述の XML
以下は実際の IdP が発行する Assertion の骨格です(署名は省略)。
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_a1b2c3d4e5f6"
Version="2.0"
IssueInstant="2026-06-12T09:30:00Z">
f9a8b7c6-1234-5678-90ab-cdef12345678
NotOnOrAfter="2026-06-12T09:35:00Z"
Recipient="https://app.example.com/saml/acs"
InResponseTo="_req-98765"/>
NotBefore="2026-06-12T09:29:00Z"
NotOnOrAfter="2026-06-12T09:35:00Z">
AuthnInstant="2026-06-12T09:30:00Z"
SessionIndex="_sess-112233">
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
要素ごとの意味と検証ポイント
| 要素 | 意味 | SP が検証すべきこと |
| --- | --- | --- |
| Issuer | Assertion 発行者(IdP)の entityID | 信頼する IdP の entityID と正確に一致するか |
| Subject/NameID | ユーザー識別子 | Format が合意済みのものか(persistent、emailAddress など) |
| SubjectConfirmation | 「この Assertion を提示する者」の条件 | Recipient が自分の ACS URL か、NotOnOrAfter が有効か、InResponseTo が自分が送った要求 ID か |
| Conditions | 有効期間と受信対象 | NotBefore/NotOnOrAfter の時間枠、Audience が自分の entityID か |
| AuthnStatement | いつ/どのように認証されたか | AuthnContextClassRef がポリシー(MFA 要求など)を満たすか |
| AttributeStatement | ユーザー属性 | マッピングルールどおりに解析し、認可の入力として使用 |
NameID Format は運用で頻繁に問題になる部分です。persistent はサービスごとに固定された不透明な識別子、transient はセッションごとに変わる使い捨て、emailAddress は人間が読めるメールアドレスです。SP が emailAddress を期待しているのに IdP が persistent を送ると、「ログインはできるのにアカウントのマッチングができない」障害が発生します。連携の初期段階で必ず合意してください。
AuthnRequest / Response のフロー
SP-initiated SSO(標準経路)
ユーザーが SP に先にアクセスした場合です。SP が AuthnRequest を作り、ブラウザを IdP へ送ります。
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_req-98765"
Version="2.0"
IssueInstant="2026-06-12T09:29:50Z"
Destination="https://idp.corp.com/saml/sso"
AssertionConsumerServiceURL="https://app.example.com/saml/acs"
ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST">
Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"
AllowCreate="true"/>
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
IdP は認証を終えると Response を返します。Response の中に Assertion が入っています。
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_resp-55555"
Version="2.0"
IssueInstant="2026-06-12T09:30:00Z"
Destination="https://app.example.com/saml/acs"
InResponseTo="_req-98765">
https://idp.corp.com/saml
<!-- 署名された saml:Assertion がここに入る -->
重要な対応関係: AuthnRequest の ID は、Response の InResponseTo、および Assertion 内 SubjectConfirmationData の InResponseTo と一致しなければなりません。この検証を省略すると、別セッション用の Response を注入する攻撃が可能になります。
SP-initiated vs IdP-initiated
SP-initiated(推奨) IdP-initiated
-------------------- --------------------
ユーザー -> SP にアクセス ユーザー -> IdP ポータルにアクセス
SP が AuthnRequest を生成(ID を記録) アプリのタイルをクリックすると
IdP 認証後に Response AuthnRequest なしでいきなり
(InResponseTo=要求 ID) Unsolicited Response を発行
SP: InResponseTo の検証が可能 SP: InResponseTo の検証が不可能
(そもそも要求が存在しない)
CSRF/注入の防御が容易 Response 注入に相対的に脆弱
IdP-initiated は「会社ポータルでアプリのアイコンをクリックして入る」UX のためエンタープライズでよく要求されますが、InResponseTo の検証ができないためセキュリティ上は劣位です。可能であれば IdP ポータルのアプリタイルが SP のログイン開始 URL を指すようにし、**実質的に SP-initiated へ迂回実装する**のがベストプラクティスです。
Binding — メッセージを運ぶ 3 つの方法
HTTP-Redirect Binding
AuthnRequest のような小さいメッセージに使います。メッセージを DEFLATE 圧縮 → base64 → URL エンコードしてクエリ文字列に載せます。
GET /saml/sso?SAMLRequest=fZJNb9swDIb%2FisG7...&RelayState=abc123
&SigAlg=http%3A%2F%2Fwww.w3.org%2F2001%2F04%2Fxmldsig-more%23rsa-sha256
&Signature=KJh8... HTTP/1.1
Host: idp.corp.com
- URL 長の制限があるため、大きなメッセージ(署名を含む Response)には不向きです。
- 署名は XML 内部ではなく**クエリパラメータ(SigAlg、Signature)**として別途渡されます(detached signature)。
HTTP-POST Binding
Response のような大きいメッセージに使います。IdP が自動送信される HTML フォームを返し、ブラウザが SP の ACS へ POST します。
- base64 のみ適用(圧縮なし)、署名は XML 内部に含まれます。
- 事実上すべての Web SSO 連携における Response の受け渡しはこのバインディングです。
HTTP-Artifact Binding
機微な内容をブラウザに露出させたくない場合に使います。ブラウザには短い参照値(artifact)だけを渡し、SP が back-channel(SOAP)で IdP から実際のメッセージを取得します。
[ブラウザ] [SP] [IdP]
|<-- artifact 受領 -| |
|--- artifact ----->| |
| |--- ArtifactResolve(SOAP)->|
| |<-- ArtifactResponse ------|
| | (実際の SAMLResponse)|
- セキュリティは高いものの、SP-IdP 間の直接のネットワーク接続が必要で実装の複雑さも高く、実務では稀です。
バインディングの比較
| 項目 | HTTP-Redirect | HTTP-POST | HTTP-Artifact |
| --- | --- | --- | --- |
| 用途 | AuthnRequest, LogoutRequest | Response の受け渡し | 高セキュリティ環境 |
| エンコーディング | DEFLATE + base64 + URL | base64 | artifact 参照値 |
| 署名の位置 | クエリパラメータ(detached) | XML 内部(enveloped) | XML 内部 |
| サイズ制限 | URL 長の制限あり | 事実上なし | 該当なし |
| back-channel の要否 | 不要 | 不要 | 必要(SOAP) |
| 実務での頻度 | 高い(要求) | 非常に高い(応答) | 低い |
Metadata — 信頼関係の設定ファイル
SAML 連携の出発点はメタデータの交換です。SP メタデータの例は次のとおりです。
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
entityID="https://app.example.com/saml/metadata">
AuthnRequestsSigned="true"
WantAssertionsSigned="true"
protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
Location="https://app.example.com/saml/slo"/>
index="0" isDefault="true"
Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://app.example.com/saml/acs"/>
IdP メタデータには SingleSignOnService エンドポイントと IdP の署名証明書が入っています。運用上のポイントは次のとおりです。
- **entityID は識別子であり URL ではありません。**慣例として URL の形を使いますが、文字列が正確に一致するかがすべてです。末尾のスラッシュ 1 つの違いで連携が壊れます。
- **証明書の期限切れが SAML 障害の原因第 1 位です。**メタデータ内の証明書は TLS 証明書とは別に期限切れになります。期限の 90/30/7 日前のアラートを自動化してください。
- **鍵のロールオーバー**: KeyDescriptor は複数置けます。新しい証明書をメタデータに追加 → 相手側の更新を確認 → 署名鍵を切り替え → 旧証明書を削除、という順序で無停止ローテーションが可能です。
- メタデータ自体に署名するか、信頼できるチャネル(管理コンソール、署名付き URL)でのみ交換してください。
XML Signature と暗号化
署名の構造
SAML は XML Signature(XMLDSig)の enveloped signature を使います。署名対象のダイジェストを計算し、そのダイジェスト情報を含む SignedInfo を秘密鍵で署名します。
注目すべきは、Reference の URI が **ID 属性の参照**である点です。「ID が _a1b2c3d4e5f6 の要素」が署名対象であることを意味し、まさにこの間接参照が Signature Wrapping 攻撃の糸口になります。
署名の範囲は Response 全体と各 Assertion のそれぞれに適用できます。**両方に署名するのが推奨**であり、最低でも Assertion への署名は必須です。
暗号化(EncryptedAssertion)
Assertion はブラウザを経由(front-channel)するため、属性に機微情報が含まれる場合は XML Encryption で Assertion を SP の公開鍵で暗号化できます。署名が「偽造防止」なら、暗号化は「内容の秘匿」です。役割が異なるため、暗号化は署名の代わりにはなりません。
XML Signature Wrapping(XSW)攻撃と防御
SAML 史上もっとも有名な攻撃の系譜です。核心となるアイデアは、**署名検証ロジックが見る「署名が有効な要素」と、アプリケーションが実際に読む要素が異なるように XML 構造を操作する**ことです。
正常な Response XSW 攻撃の Response
-------------- ------------------
Response Response
└─ Assertion (ID=A, 署名済み) ├─ [偽造 Assertion] (ID=B, 署名なし)
└─ Subject: alice │ └─ Subject: admin <-- アプリが読むもの
└─ [元の Assertion] (ID=A, 署名は有効)
└─ Subject: alice <-- 検証器が見るもの
署名検証器: 「ID=A の要素の署名? 有効」 --> 通過
アプリケーション: 最初の Assertion(偽造)を解析 --> admin としてログイン
2012 年の研究では、当時の主要な SAML ライブラリ 14 個のうち多数がこの系統の攻撃に破られ、その後も亜種が周期的に発見されています。
防御チェックリスト
1. **自作しないこと** — 検証済みライブラリ(OpenSAML など)の最新版を使い、セキュリティパッチを追跡します。
2. **「署名されたまさにそのノード」だけを使う** — 署名検証に成功した要素の DOM ノードからのみデータを読みます。「文書から最初の Assertion を探す」式の解析は厳禁です。
3. **スキーマ検証を先に** — SAML スキーマに反する構造(重複 Assertion、おかしな位置の要素)を署名検証の前に拒否します。
4. **Assertion の個数を強制** — Web SSO では Assertion は 1 つです。2 つ以上なら拒否します。
5. **Response と Assertion の両方に署名を要求** — WantAssertionsSigned と Response 署名の要求を両方有効にします。
6. **XML パーサーのハードニング** — DTD と外部エンティティ(XXE)を無効化します。
RelayState — 忘れられがちな脇役
RelayState は「ログイン後にどこへ戻るか」を運ぶ不透明なパラメータです。SP-initiated では SP が送った値が Response とともにそのまま戻り、IdP-initiated では IdP が目的地の URL を入れることもあります。
運用/セキュリティのポイント:
- スペック上 80 バイトの制限があるため、URL 全体ではなく**サーバー側状態へのキー**を入れるのが安全です。
- 戻ってきた RelayState を検証なしにリダイレクトに使うと **open redirect** の脆弱性になります。ホワイトリストまたは署名/サーバー側照会で検証してください。
- RelayState は署名の対象外なので、改ざんされうる前提で扱う必要があります。
クロックスキュー — 間欠的障害の常連犯
Assertion の NotBefore/NotOnOrAfter は通常、発行時刻を基準に ±数分の短い窓です。IdP と SP の時計がずれるとこうなります。
IdP の時計: 09:30:00 --> NotBefore=09:29:00 で発行
SP の時計: 09:28:30 --> 「NotBefore が未来」 --> 拒否
症状: 同じユーザーがリトライすると成功することもある(間欠的)
時計がずれた特定の SP ノードにルーティングされた時だけ失敗
対応:
1. すべてのノードに NTP/chrony を強制し、ドリフトを監視します。
2. SAML ライブラリの clock skew 許容値を 60〜120 秒に設定します(多くはデフォルト 0 か非常に小さい値)。
3. 障害分析のために「どのノードで失敗したか」と当該ノードの時刻を併せて記録するログを整備します。
検証失敗ログには最低限、Issuer、InResponseTo、拒否理由(時刻条件/Audience/署名)、そしてサーバー時刻を残してください。「Invalid SAML response」一行だけのログは運用者への拷問です。
SP 実装時の検証チェックリスト
SAML Response 受信時(ACS エンドポイント):
[ ] 1. ハードニング済み XML パーサーで解析(DTD/XXE 遮断)
[ ] 2. スキーマ検証
[ ] 3. Response の署名検証(要求している場合)
[ ] 4. Status が Success であることを確認
[ ] 5. Assertion の署名検証 — 信頼された IdP 証明書で
[ ] 6. 以降のデータは署名されたノードからのみ読む
[ ] 7. Issuer が期待した IdP の entityID か
[ ] 8. Conditions: NotBefore/NotOnOrAfter(skew 許容値込み)
[ ] 9. AudienceRestriction が自分の entityID か
[ ] 10. SubjectConfirmationData: Recipient が自分の ACS URL か、
NotOnOrAfter が有効か、InResponseTo が自分が発行した要求 ID か
[ ] 11. Assertion ID の再利用チェック(リプレイ防止キャッシュ)
[ ] 12. NameID Format が合意済みの形式か
[ ] 13. すべての検証を通過した後にのみアプリセッションを確立
SAML が 2026 年も生きている理由
1. **B2B 信頼関係の慣性** — 数万社の企業 IdP と SaaS がすでに SAML で接続されています。動いている信頼関係を撤去するビジネス上の動機は弱いのです。
2. **調達要件** — エンタープライズ SaaS の購買チェックリストに「SAML SSO 対応」が今も明記されます。SSO をプレミアムプランに閉じ込める慣行(いわゆる SSO tax)への批判があること自体、SAML が標準要件である証左です。
3. **レガシー IdP エコシステム** — Broadcom SiteMinder(現行 12.9)のようなレガシー WAM 製品群が今も大企業で稼働しており、これらの標準的な連携経路が SAML です。ヘッダーベース認証から標準プロトコルへの移行の過程でも、SAML が最初の停車駅になることが多いのです。
4. **プロトコル自体の完結性** — Web SSO という用途に限れば、SAML はすでに完成したプロトコルです。変化がないことは安定性でもあります。
ただし方向性は明確です。新機能(passkeys 連携、トークン交換、FAPI など)はすべて OIDC 陣営で起きており、Keycloak のようなモダン IdP は SAML と OIDC の両方をサポートするため、**IdP をハブに置き、レガシー SP は SAML、新規アプリは OIDC** で接続するハイブリッドが 2026 年の標準アーキテクチャです。
[Keycloak 26.6 / Okta / Entra ID]
| |
SAML 2.0 | | OIDC
v v
[レガシー/B2B SaaS] [新規 Web/モバイル/API]
おわりに
SAML 2.0 を一文で要約すると「XML Assertion をブラウザのリダイレクトとフォーム POST で運び、XML Signature で信頼を保証する Web SSO プロトコル」です。実務で覚えておくべきことは 3 つです。
- **Assertion の条件(時刻、Audience、Recipient、InResponseTo)を 1 つも漏らさず検証**しなければなりません。署名検証だけでは不十分です。
- **Signature Wrapping の教訓**: 署名が有効なノードとデータを読むノードは同一でなければなりません。自作せず、検証済みライブラリを使ってください。
- **運用障害の三大要因**は証明書の期限切れ、クロックスキュー、entityID/URL の不一致です。いずれも監視と自動化で予防できます。
次回の記事では OIDC の内部 — Authorization Code Flow、Discovery、JWKS、トークン検証 — を同じ深さで扱います。
参考資料
- [SAML 2.0 Core Specification (OASIS)](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)
- [SAML 2.0 Bindings (OASIS)](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf)
- [SAML 2.0 Profiles (OASIS)](https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf)
- [SAML 2.0 Metadata (OASIS)](https://docs.oasis-open.org/security/saml/v2.0/saml-metadata-2.0-os.pdf)
- [XML Signature Syntax and Processing (W3C)](https://www.w3.org/TR/xmldsig-core1/)
- [XML Encryption Syntax and Processing (W3C)](https://www.w3.org/TR/xmlenc-core1/)
- [On Breaking SAML: Be Whoever You Want to Be (USENIX Security 2012)](https://www.usenix.org/conference/usenixsecurity12/technical-sessions/presentation/somorovsky)
- [OWASP SAML Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SAML_Security_Cheat_Sheet.html)
- [Keycloak Documentation — SAML Clients](https://www.keycloak.org/documentation)
- [Keycloak Release Notes](https://www.keycloak.org/docs/latest/release_notes/index.html)
- [Broadcom SiteMinder Documentation](https://techdocs.broadcom.com/siteminder)
- [OpenID Connect Core 1.0(比較参考)](https://openid.net/specs/openid-connect-core-1_0.html)
현재 단락 (1/196)
OIDC が新規構築のデフォルトとなった 2026 年でも、エンタープライズ B2B SSO の現場では依然として SAML 2.0 が共通語です。顧客企業の Entra ID、Okta、あるいは自社...