---
title: "SAMLアサーションとは：XML構造の読み方と署名・Audience・有効期限の検証実装"
url: "https://www.issoh.co.jp/tech/details/19341/"
published: 2026-11-05
updated: 2026-11-05
categories: ["セキュリティ"]
publisher: "株式会社一創"
---

# SAMLアサーションとは：XML構造の読み方と署名・Audience・有効期限の検証実装

SAMLアサーションは、IdPが「この利用者を認証した」という事実と属性をXMLで記述し、署名を付けてSPへ渡す文書です。この記事では、SAML Responseに包まれて届くアサーションを要素ごとに読み解き、SPが署名・Recipient・Audience・有効期限・リプレイをどの順序で検証すべきかを、OASIS SAML 2.0の仕様書とPythonのコードで示します。検証漏れを突く署名ラッピング攻撃と、自前で書く範囲の判断も扱います。

## まとめ：SAMLアサーションの中身とSPが検証する順序の要点

アサーションは、発行者のIssuer、利用者と宛先のSubject、期間と相手を縛るConditions、認証の事実のAuthnStatement、属性を運ぶAttributeStatementで構成されます。SAML Responseはその封筒で、HTTP-POSTで届く場合、仕様は中のアサーション自体に署名を求めています。

SPの検証は、署名を確かめてから中身を読む順番が鉄則。署名が守る要素とアプリが読む要素が同じだと確かめないと、署名ラッピング攻撃で別人としてログインされます。そのうえでRecipient、NotOnOrAfter、InResponseTo、Audienceを照合し、使ったアサーションIDを期限まで保存して二度目を拒否します。

署名検証とXMLの正規化は自分で書かず、ライブラリに任せてください。自前で持つのは、リプレイ防止のID保存と、属性を利用者へ結び付ける処理だけで足ります。

## SAMLアサーションの役割とResponseに包まれて届くXMLの全体像

どの封筒に入って届くのかを押さえると、署名の位置を取り違えずに済みます。SAMLの定義やSP-Initiatedの流れは[SAMLとは？認証フロー・IdP/SPの仕組みとOAuth・OIDCとの使い分けを実装視点で解説](https://www.issoh.co.jp/tech/details/13299/)で扱っているので、ここでは届いたXMLの中身に絞ります。

### ResponseとAssertionの入れ子関係と署名が付く2か所の位置の違い

SPのACS（Assertion Consumer Service）に届くのは`samlp:Response`要素で、その中に1つ以上の`saml:Assertion`が入ります。名前空間も分かれていて、Responseはプロトコル側の`urn:oasis:names:tc:SAML:2.0:protocol`、Assertionはアサーション側の`urn:oasis:names:tc:SAML:2.0:assertion`に属します。

署名はResponseとAssertionのどちらにも付けられます。ただし[OASIS SAML 2.0 Profiles仕様](https://docs.oasis-open.org/security/saml/v2.0/saml-profiles-2.0-os.pdf)のSection 4.1.4.5は、HTTP-POSTバインディングで届ける場合、中のアサーションへの署名を必須としています。Responseだけに署名するIdP設定は、この規定から外れる状態です。どちらに署名があるかは、`ds:Signature`要素がResponseとAssertionのどちらの直下にあるかで判別してください。

### Assertion要素の必須属性と子要素の並び順をCore仕様の2.3.3で確認

[OASIS SAML 2.0 Core仕様](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf)のSection 2.3.3によると、Assertionの必須属性はVersion（値は2.0）、ID、IssueInstant（UTCの発行時刻）の3つです。子要素のうち必須はIssuerだけで、Signature、Subject、Conditions、Advice、各種ステートメントは省略できる扱いになっています。

ただし仕様上の省略可と、SSOで使えるかは別問題です。Web Browser SSOプロファイルでは、少なくとも1つのアサーションにAuthnStatementとbearer方式のSubjectConfirmationが必須になります。次のXMLは、SP-Initiatedのログインで届く典型的なアサーションを、署名の中身を省いて整形したものです。

```
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="_a75adf55-01d7-40cc-929f-dbd8372ebdfc" Version="2.0"
    IssueInstant="2026-11-05T01:00:00Z">
  <saml:Issuer>https://idp.example.jp/metadata</saml:Issuer>
  <ds:Signature>（署名値と証明書。省略）</ds:Signature>
  <saml:Subject>
    <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">3f7b2c9e8a1d</saml:NameID>
    <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData Recipient="https://sp.example.jp/saml/acs"
          InResponseTo="_req-4f1c" NotOnOrAfter="2026-11-05T01:05:00Z"/>
    </saml:SubjectConfirmation>
  </saml:Subject>
  <saml:Conditions NotBefore="2026-11-05T00:59:30Z" NotOnOrAfter="2026-11-05T02:00:00Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://sp.example.jp/saml/metadata</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement AuthnInstant="2026-11-05T00:59:58Z" SessionIndex="_sess-81d2">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
  <saml:AttributeStatement>
    <saml:Attribute Name="department">
      <saml:AttributeValue>sales</saml:AttributeValue>
    </saml:Attribute>
  </saml:AttributeStatement>
</saml:Assertion>
```

IDが英字かアンダースコアで始まるのは、型がXML Schemaのxs:IDで、数字から始められないためです。IssuerとSignatureが先頭に並ぶ順序もスキーマで固定されています。

## Subject・Conditions・Statementの各要素を実データで読む手順

上のXMLの各要素は「誰の」「どこ宛ての」「いつまで有効な」「どう認証された」情報かを分担しており、SPが照合する値はほぼこの4か所に集まります。

### bearer確認のRecipientとInResponseToが縛る宛先と要求の対応

Subjectの中身は、利用者を表すNameIDと、アサーションを提示した者が本人だと確かめる方法を表すSubjectConfirmationの2つです。ブラウザSSOで使うのは`urn:oasis:names:tc:SAML:2.0:cm:bearer`で、持参した者を本人とみなす方式になります。持参しただけで通る以上、宛先と期限の縛りが唯一の防壁です。

Profiles仕様のSection 4.1.4.2は、bearerのSubjectConfirmationDataに次の3つを求めています。

- Recipient：SPのACS URL。別のSP宛てに発行されたアサーションの持ち込みを防ぐ
- NotOnOrAfter：アサーションを届けてよい期限。NotBeforeは書いてはならない
- InResponseTo：SPが送ったAuthnRequestのID。IdP-Initiatedでは付かない

SPはAuthnRequestのIDをセッションへ保存し、戻ってきたInResponseToと照合します。省くと、攻撃者が自分のアカウントで得たアサーションを被害者のブラウザへ送り込むログインCSRFが成立します。NameIDの形式ごとの扱いや不一致の切り分けは[SAML NameIDFormatとメタデータの不一致でSSOが失敗する原因の切り分け](https://www.issoh.co.jp/tech/details/17589/)にまとめました。

### NotOnOrAfterとAudienceRestrictionの判定と時刻ずれの幅

Conditionsは、アサーションを使ってよい期間と相手の条件です。Core仕様のSection 2.5.1.1は判定結果をValid、Invalid、Indeterminateの3値で定め、理解できない条件要素が1つでもあればIndeterminateとし、InvalidとIndeterminateはどちらも拒否すると規定しています。知らない条件を読み飛ばして通す実装は仕様違反です。

期限は2か所にあります。ConditionsのNotOnOrAfterはアサーション自体の有効期間、SubjectConfirmationDataのNotOnOrAfterは届けてよい期限で、後者が短いのが普通です。AudienceRestrictionは、SPのentityIDがAudienceに含まれる場合だけValidになります。

時刻ずれについて、[Microsoft LearnのSAML 2.0トークン要求リファレンス](https://learn.microsoft.com/ja-jp/entra/identity-platform/reference-saml-tokens)は、Microsoft Entra IDとの時計のずれを最大5分まで許容してよいとしています。これは上限で、NTPで同期できていれば数十秒から1分程度で足ります。

### AuthnStatementのSessionIndexと認証方式の記録が効く場面

AuthnStatementは、IdPで認証が行われたという事実の記録です。Core仕様のSection 2.7.2では、認証時刻のAuthnInstantが必須、SessionIndexとSessionNotOnOrAfterが任意の属性となっています。

SessionIndexは、IdPがシングルログアウトに対応している場合に必須となり、ログアウト要求で終わらせるセッションの特定に使われます。SessionNotOnOrAfterが付いていれば、SPはその時刻でセッションを破棄すべきとProfiles仕様のSection 4.1.4.3が定めています。AuthnContextClassRefには、パスワードのみか多要素かといった認証方式が入る仕組み。管理画面だけ多要素認証を必須にしたいなら、この値を見て再認証を求められます。URIはIdP製品ごとに違うため、受け入れる値はIdPごとに設定で持たせてください。

### AttributeStatementの属性名URIとグループ属性150件の上限

AttributeStatementには、氏名、メールアドレス、所属グループなどの属性が`saml:Attribute`要素で並びます。属性名は仕様で固定されておらず、IdPごとに表記が違う点が実装の手間になるところ。Microsoft Entra IDなら、オブジェクトIDは`http://schemas.microsoft.com/identity/claims/objectidentifier`という長いURIで届きます。

前述のMicrosoft Learnのリファレンスによると、所属グループがSAMLで150件を超えると、グループ一覧の代わりにMicrosoft Graphの参照先が入ります。グループで権限を判定する実装がこの超過を想定していないと、権限が全部外れる事態になります。同じページは、メールアドレスを認可に使う危険性にも触れており、利用者の特定にはオブジェクトIDのような変更不可の値を使うのが安全です。

属性名の読み替えをIdPごとにどう持つかは、[Cognito SAML連携の手順：Entra IDでSSOを組む設定と属性マッピング](https://www.issoh.co.jp/tech/details/18512/)に具体例があります。IdPとSPの間でどの属性を渡すかを信頼関係として決める考え方は、[フェデレーション認証とは？IdPとSPの信頼設計と属性連携を実装視点で解説](https://www.issoh.co.jp/tech/details/16413/)で整理しています。

## SPがアサーションを受け取ってから検証する順序とPython実装

要素の意味が分かったら、次はSPの受信処理です。検証項目を並べるだけでなく、どの順で確かめるかに意味があります。

### Web Browser SSOプロファイルが定める検証項目と処理の順番

Profiles仕様のSection 4.1.4.3は、バインディングにかかわらずSPが行う検証を列挙しています。これに[OWASPのSAML Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/SAML%5FSecurity%5FCheat%5FSheet.html)が勧める項目を加え、実装の順番に並べ直すと次のようになります。

1. スキーマ検証を通し、構造が仕様どおりかを確かめる
2. 事前に登録したIdPの証明書で署名を検証する。文書内のKeyInfoにある証明書は信用しない
3. 署名の参照先IDが、これから処理するAssertionのIDと一致するかを確かめる
4. ResponseのDestinationと、bearer確認のRecipientがACS URLと一致するかを見る
5. InResponseToを保存済みのAuthnRequestのIDと照合する
6. 2か所のNotOnOrAfterとNotBefore、AudienceRestrictionを評価する
7. アサーションIDが使用済みでないかを確かめ、使用済みとして記録する

署名検証より先に属性を読む実装が最も危険です。検証前の値でユーザーを引くと、検証で落ちても副作用が残ります。OWASPは、SHA-1系の署名を拒否しRSA-SHA256以上を要求することも勧めています。

### python3-samlのstrictモードと署名要求で検証を任せる設定

Pythonでは[python3-saml](https://github.com/SAML-Toolkits/python3-saml)が上記の大半を実装しています。PyPIの最新版は2023年10月公開の1.16.0系で、READMEによれば1.8.0からstrictモードが既定で有効です。strictがtrueなら、署名の欠けたメッセージやDestination、Conditionsの条件を満たさないメッセージを拒否します。検証の強さを決める設定は次のとおりです。

```
{
  "strict": true,
  "security": {
    "wantMessagesSigned": false,
    "wantAssertionsSigned": true,
    "wantAssertionsEncrypted": false,
    "wantNameId": true,
    "rejectDeprecatedAlgorithm": true,
    "signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
    "digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
  }
}
```

wantAssertionsSignedが、Profiles仕様のアサーション署名の規定に対応する設定です。wantMessagesSignedはResponseへの署名を要求するため、IdPがResponseに署名しない設定だと全ログインが落ちます。rejectDeprecatedAlgorithmは、SHA-1系で署名されたメッセージを拒否する設定です。

### アサーションIDを有効期限まで保存してリプレイを拒否する実装例

Profiles仕様のSection 4.1.4.5は、SPがbearerアサーションの再利用を防ぐために、使用済みのIDをSubjectConfirmationDataのNotOnOrAfterまで保持しなければならないと定めています。python3-samlはこれを自動では行いません。READMEも、処理済みのIDを保存して照合する処理を利用側で書くよう案内しています。

次はFlaskとRedisで書いたACSの例です。SETのNXオプションで「未登録なら登録」を1回で行うため、同時に2回届いても片方しか通りません。

```
import time
import redis
from flask import Flask, request, session, redirect, abort
from onelogin.saml2.auth import OneLogin_Saml2_Auth

app = Flask(__name__)
app.secret_key = "change-me"
store = redis.Redis()

def saml_req():
    return {
        "https": "on",
        "http_host": request.host,
        "script_name": request.path,
        "get_data": request.args.copy(),
        "post_data": request.form.copy(),
    }

@app.post("/saml/acs")
def acs():
    # saml ディレクトリに settings.json と advanced_settings.json を置く
    auth = OneLogin_Saml2_Auth(saml_req(), custom_base_path="saml")
    # ログイン開始時に保存した AuthnRequest の ID を渡し、InResponseTo を照合させる
    auth.process_response(request_id=session.pop("authn_request_id", None))
    if auth.get_errors() or not auth.is_authenticated():
        # エラーコードだけを記録し、受信した値そのものはログに出さない
        app.logger.warning("SAML rejected: %s", auth.get_errors())
        abort(401)

    # 使用済みのアサーションIDは NotOnOrAfter まで保持して二度目を拒否する
    aid = auth.get_last_assertion_id()
    expires = auth.get_last_assertion_not_on_or_after() or time.time() + 300
    ttl = max(int(expires - time.time()), 1)
    if not store.set("saml:aid:" + aid, 1, nx=True, ex=ttl):
        abort(401)

    session["saml_nameid"] = auth.get_nameid()
    session["saml_attrs"] = auth.get_attributes()
    return redirect("/")
```

ログイン開始側では`auth.login()`の直後に`auth.get_last_request_id()`でIDを取り、セッションへ保存します。

## 署名ラッピング攻撃で検証をすり抜けられる仕組みと実装側の防ぎ方

署名を検証しているのに偽のアサーションが通る。SAMLで繰り返し見つかってきた脆弱性は、ほぼこの形をしています。

### 署名済み要素と処理する要素がずれるXML Signature Wrapping

XMLの署名は、[W3CのXML Signature 1.1](https://www.w3.org/TR/xmldsig-core1/)が定める仕組みで、SignedInfo内のReference要素がURI属性で署名対象の要素をIDで指します。SAMLでは`URI="#_a75adf55..."`のように、Assertionの ID属性を参照します。

XML Signature Wrapping（XSW）は、正規の署名付きアサーションを文書の別の場所へ移し、元の位置に偽のアサーションを置く攻撃です。署名検証はIDで正規の要素を見つけて合格とし、アプリは決め打ちの位置にある偽物を読みます。2012年のUSENIX Securityで発表された[On Breaking SAML: Be Whoever You Want to Be](https://www.usenix.org/conference/usenixsecurity12/technical-sessions/presentation/somorovsky)は、主要なSAMLフレームワーク14種のうち11種にこの脆弱性があったと報告しています。

### ruby-samlのCVE-2025-25291に見るXMLパーサー併用の落とし穴

この種の脆弱性は過去のものではありません。GitHub Security Labは2025年3月12日の記事[Sign in as anyone: Bypassing SAML SSO authentication with parser differentials](https://github.blog/security/sign-in-as-anyone-bypassing-saml-sso-authentication-with-parser-differentials/)で、ruby-samlの1.17.0までに認証回避の脆弱性CVE-2025-25291とCVE-2025-25292があり、1.18.0で修正されたと公表しました。

原因は、署名検証でREXMLとNokogiriという2つのXMLパーサーを併用していたことです。片方で取り出した要素のダイジェストを、もう片方で取り出したダイジェスト値と署名値に突き合わせていたため、解釈の差を突くとハッシュの検証と署名の検証が別々の要素を見る状態を作れました。有効な署名を1つ入手すれば、任意の利用者になりすませたとされています。ライブラリを使っていても、更新を止めれば守られません。omniauth-samlのように間接的に依存している場合も、依存先の版を確認してください。

### 署名の参照IDと処理対象の一致を確かめるxmlsec1での検証手順

OWASPのCheat Sheetが挙げるXSW対策の要点は、署名のReference URIが、認証に使うAssertionそのものを指していることの確認です。加えて、要素は絶対パスで選び、検証前の文書から`getElementsByTagName`のような名前検索でセキュリティ上の要素を拾わないよう求めています。

届いたアサーションの署名を手元で検証したいときは、アサーションを単体のファイルに切り出し、IdPの署名証明書を指定してxmlsec1を実行します。

```
xmlsec1 --verify --pubkey-cert-pem idp-signing.crt \
  --id-attr:ID urn:oasis:names:tc:SAML:2.0:assertion:Assertion \
  assertion.xml
```

`--id-attr`は、Assertion要素のID属性をXMLのIDとして扱わせるオプションです。OKと出ても、証明されるのは署名対象が改ざんされていないことだけ。アプリが読む要素が署名対象と同じかどうかは、実装側で別に保証します。

## アサーション検証を自前で書く範囲とライブラリに任せる範囲の判断基準

ここまでの検証を全部自前で書くか、どこまでライブラリに任せるか。条件を示して結論を書きます。

### 署名検証とXML正規化を自前で書かないと決めるべき条件と例外

署名検証、XMLの正規化、参照IDの解決は自前で書きません。XSWとパーサー差異の事例が示すとおり、仕様を正しく読んだつもりでも、処理系の挙動差1つで認証回避が成立します。標準XMLライブラリと署名ライブラリを組み合わせて自作する構成は、レビューで止めるべき設計です。

自前で書いてよいのは次の範囲に限られます。

- 使用済みアサーションIDの保存と照合（多くのライブラリが利用側に任せている）
- 属性をアプリの利用者や権限へ結び付ける処理と、グループ超過時の扱い
- AuthnContextClassRefを見た再認証の要否判定

既存ライブラリが対応していないIdPと繋ぐ必要がある場合は、自作ではなく、IdPの手前に対応済みのIDaaSを置いて変換させる構成を先に検討してください。

### アサーション暗号化とIdP-Initiatedを有効にするかの判断基準

アサーションの暗号化は、ブラウザに属性が見えても困らない用途なら必須ではありません。HTTP-POSTで届くSAMLResponseは、開発者ツールでbase64デコードするだけで読めます。健康情報のように端末に平文で残したくない属性を運ぶ場合に限り、暗号化を有効にします。HTTP-POSTとHTTP-Artifactで署名の要件がどう変わるかは「[SAML HTTP POSTバインディングとは：Redirect・Artifactとの違い](https://www.issoh.co.jp/tech/details/19345/)」を参照してください。

IdP-Initiatedは、要件で求められない限り無効にします。InResponseToで照合できず、OWASPもUnsolicited Responseは設計上安全性が低いと明記しています。必要ならリプレイ検知とRelayStateの許可リスト検証を入れたうえで有効にしてください。未要求応答を見分けて検証するコードとIdP側の設定は「[SAML IdP-initiatedとは：SP-initiatedとの違いとACS実装の判断](https://www.issoh.co.jp/tech/details/19346/)」にまとめています。

既存の業務システムや会員基盤に、SAMLの検証ロジックやIdP連携をどう組み込むか迷う段階であれば、[認証基盤・ID管理システム開発](https://www.issoh.co.jp/service/system/identity/)として、方式選定から実装・検証まで対応しています。

## よくある質問

SAMLアサーションの実装で繰り返し出てくる質問を、仕様書の記述に沿って整理しました。

### SAMLアサーションとSAMLレスポンスの違いは何ですか？

SAMLレスポンスは、IdPがSPへ返すメッセージで、処理結果のStatusとアサーションを入れる封筒です。アサーションはその中身で、誰がいつどう認証されたかと属性を記述します。HTTP-POSTの場合、Profiles仕様はアサーション側への署名を必須としています。

### SAMLアサーションの有効期限はどれくらいですか？

仕様に固定の長さはなく、IdPが決めます。期限はConditionsとSubjectConfirmationDataの2か所にあり、後者のSPへ届けてよい期限は数分程度が多いため、SPのサーバー時刻がずれると正しいアサーションでも拒否されます。

### SAMLのAudienceには何を設定しますか？

SPのentityIDです。IdPがAudienceRestriction内にこの値を入れ、SPは自分のentityIDと完全一致するかを検証します。実在するURLである必要はありませんが、末尾のスラッシュ1文字の差でも不一致になるため、メタデータからコピーして揃えてください。entityIDを含むメタデータ全体の構造は「[SAMLメタデータとは：EntityDescriptorの読み方と署名証明書の更新手順](https://www.issoh.co.jp/tech/details/19343/)」で読み解いています。

### SAMLアサーションは暗号化すべきですか？

HTTPSで送っていても、アサーションはブラウザを経由するため利用者の端末では平文で読めます。氏名や部署程度なら暗号化しない構成が一般的です。機微な情報を運ぶ場合はEncryptedAssertionで暗号化し、SP側で復号用の秘密鍵を管理します。

### SAMLアサーションとJWTの違いは何ですか？

どちらも署名付きで認証の事実を運ぶ点は同じですが、SAMLアサーションはXMLとXML Signature、OIDCのIDトークンはJSONとJWSで表現されます。XMLの正規化が要らないぶん、JWTは検証処理が単純でモバイルアプリやSPAとの相性がよい形式です。IDトークンの検証手順は[OIDC（OpenID Connect）とは？仕組み・IDトークンの検証手順とOAuth 2.0との違い](https://www.issoh.co.jp/tech/details/6532/)で解説しています。

## 関連記事

- [SAMLとは？認証フロー・IdP/SPの仕組みとOAuth・OIDCとの使い分けを実装視点で解説](https://www.issoh.co.jp/tech/details/13299/)：SAMLの定義と認証フローを扱う親記事
- [SAML NameIDFormatとメタデータの不一致でSSOが失敗する原因の切り分け](https://www.issoh.co.jp/tech/details/17589/)：アサーションが通らないときの切り分け手順
- [フェデレーション認証とは？IdPとSPの信頼設計と属性連携を実装視点で解説](https://www.issoh.co.jp/tech/details/16413/)：IdPとSPの信頼関係と属性連携の設計
- [Cognito SAML連携の手順：Entra IDでSSOを組む設定と属性マッピング](https://www.issoh.co.jp/tech/details/18512/)：Entra IDの属性を読み替える設定例
- [IDaaSとは？SSO・IdPとの違いとSAML・OIDC・SCIM連携を実装視点で解説](https://www.issoh.co.jp/tech/details/16401/)：IdP側を製品で用意する場合の選び方

---

出典: [SAMLアサーションとは：XML構造の読み方と署名・Audience・有効期限の検証実装](<https://www.issoh.co.jp/tech/details/19341/>)（株式会社一創）
