Skip to content

MS_SAMLImplementation

nishi_74322014 edited this page Aug 12, 2026 · 1 revision

SAMLを実装する。

概要

汎用認証サイト(Multi-purpose Authentication Site)
に SAML 2.0 を実装するため作成。

スコープ

SAML の仕様は膨大だが、使われていないものも多い。
以下の範囲に絞れば、実装はワリとスリムになる。

区分 採用
Profile SP Initiated な Web Browser SSO Profile / Federation Using Out-of-Band Account Linking
Binding Authentication Request : HTTP Redirect Binding / Authentication Response : HTTP POST Binding
Core Protocols : Authentication Request Protocol

補足: この絞り込みは実務的にも妥当である。
主要な SaaS(Microsoft 365、Google Workspace、Salesforce、Box、Cybozu 等)は
この組み合わせしか使わない

「Request は Redirect、Response は POST」という非対称な選択には理由があり、

  • Request は短いので URL に載せられる(302 で済む)
  • Response(アサーション)は長く、かつ
    URL に載せるとログ・履歴・Referer に残るので POST にする

SAML Bindings を参照)。

メタデータ

  • SP 側はメタデータを公開しない。
  • IdP 側はメタデータを公開する。
    • メタデータは仕様提示と設定入力支援にのみ使用する。
    • メタデータで指定された範囲の仕様で応答する(一意になるオプションは不要)。
    • SAML Metadata で、<AuthnRequest> に指定可能な内容を示す。

トラストサークル

  • 仕様上、トラストサークルと言う用語は出てこない。

  • 単純に「IdP-SP 間で、お互いを事前に登録し、お互いの証明書を交換する。」こと。

  • ただ、Microsoft Entra ID や Cybozu の仕様を見ると、
    Request に署名しているケースは限られている
    (イントラからのフェデレーションでは Request にも署名しているものと思われる)。

署名

前述のトラストサークルの慣例から、リクエスト・レスポンス共に署名を行う。

補足(Request 署名の要否): <AuthnRequest> の署名は
必須ではない。理由は次の通り。

  • <AuthnRequest> に機密情報は無い(「ログインさせてくれ」という要求のみ)。
  • IdP はどのみち認証画面を出すので、偽の要求で害が生じにくい。
  • IdP 側が SP 証明書の管理を強いられ、運用が重くなる

ただし、要求署名が意味を持つ場面はある。

  • 未署名だと AssertionConsumerServiceURL を改ざんされる
    (ただしこれは「メタデータ登録済みの URL とだけ一致させる」ことで塞げる)
  • <RequestedAuthnContext>(認証強度の指定)を改ざんされ、
    強い認証を要求したつもりが弱い認証で通る

後者があるため、高保証が要る系では要求も署名するのが正しい。

スコープ外

Web Browser SSO 以外の Profile

SAML は、Web Browser SSO Profile 以外で使われていない。

HTTP Artifact Binding

IdM 実験室によると…。

昔はデータサイズの問題で、Artifact Binding を使っていたが
最近は通信速度や端末性能の向上により、使われることは減ってきているらしい。

SP と IdP の間で直接通信をさせなければならないので、
クラウドサービスと社内 IdP を連携させる場合など面倒なため。

ただ、今でも稀に Artifact Binding しか使えない SP が
存在するので、仕方なく(IdP 側に)実装することがある。

なお、AD FS では Artifact Binding をサポートしている。
Microsoft Entra ID は POST Binding のみで
Artifact Binding は使えない。

詳細

プロトコル&アサーション

AuthnRequest

  • <NameIDPolicy>

    • Format
採否 Format 内容
unspecified 既定値(ユーザ名)
entity Public UserID
emailAddress メアド
persistent 永続的仮名 - Pairwise UserID
transient 一時的仮名
☓☓ X509SubjectName
☓☓ WindowsDomainQualifiedName
☓☓ kerberos
  • AllowCreate="false"

  • <RequestedAuthnContext>(多くの SP では使用していない)

採否 Class
unspecified(不特定の方法で)
PasswordProtectedTransport(パスワード)
PasswordPasswordProtectedTransport との違いが?)
PreviousSession(既存のセッションを利用)
X509 / SecureRemotePassword / TimeSyncToken
Smartcard / SmartcardPKI / SoftwarePKI
MobileOneFactorContract / MobileOneFactorUnregistered
MobileTwoFactorContract / MobileTwoFactorUnregistered
PGP / SPKI / XMLDSig / TLSClient / Kerberos
Telephony / NomadTelephony / AuthenticatedTelephony / PersonalTelephony
InternetProtocol / InternetProtocolPassword

補足(PasswordPasswordProtectedTransport の違い): 元ページの
「違いが?」という疑問に答えておく。

Class 意味
Password パスワードを提示して認証した。通信路の保護は問わない
PasswordProtectedTransport パスワードを提示して認証し、かつ通信路が保護されている(HTTPS)

つまり「HTTPS だったかどうか」の一点である。
現在はすべて HTTPS なので、実質的に
PasswordProtectedTransport を使えばよい

Assertion / Response

例を参考にする(後述)。

エンコード・デコード

samltool.io からアサーションを取得し、SAMLTool.com で確認するなど。

署名・検証のポイント

ネストした複数のXML署名が Verify できない

SignedXml クラスの実装に問題があり、
ネストした複数の XML 署名が Verify できないという問題があるらしい。

  • SAML は Assertion 側と Response 側どちらでの <Signature> の存在を
    仕様上は認めている。
  • そのため IdP(またはその設定)によっては両方に XML 署名が存在する場合がある。
  • ネストしている場合の正規化では、外側の <Signature> を取り除き、内部は残しておく。
  • System.Security.Cryptography.SignedXml クラスの実装に問題がある。
    (ネストした <Signature> を扱うと Verify できないバグがある)

※ 外側だけ検証すればイイ気がする。
 後述が、SignedXml によるネストした XML 署名・検証結果。

補足(「外側だけ検証すればイイ」の是非): 結論から言うと、
「外側だけ」で構わないが、条件がある

<Response> の署名を検証したなら、
その署名対象ノードの配下にある <Assertion> を使う限り、
内側の署名を重ねて検証する必要はない(完全性は担保されている)。

ただし、次をやってはならない。

  • 文書全体を XPath で再検索して <Assertion> を取り出す
    (署名対象外の <Assertion> を掴む= XSW)
  • 「どちらか署名があればよい」として、署名の有無を確認せずに
    内側を読む

実装上は「署名検証に成功したノードのオブジェクト参照を保持し、
そこからしか値を読まない
」という形にするのが確実である。

Whitespace を保持したままのXML署名が Verify できない

XmlDocument を利用する際に PreserveWhitespace を単に true にする。

補足: XmlDocument の既定(PreserveWhitespace = false)では
意味を持たない空白が捨てられるため、
受信したバイト列と正規化結果が食い違い、ダイジェストが一致しなくなる。

同様の理由で、受信した XML を整形(pretty print)してはならない
ログ出力のために整形したものを検証に回す、という事故が起きやすい。

SAML v2 schema(XSD) による SAML(XML) の検証

以下を用いて、SAML を検証する。

しかし、結局 XPath で検証していたりする。
(適切な、ミニマムの XSD をオンラインで発見できないため)

補足(スキーマ検証は「あった方がよい」): スキーマ検証は
XSW 攻撃に対する多層防御の一つになる
ID の重複や不正な位置の要素を弾ける)。

ただし .NET でスキーマ検証を行う際は、

  • XSD をオンラインで取りに行かない(ローカルに同梱する)
  • DTD 処理を無効化するDtdProcessing = Prohibit。XXE 対策)
  • XmlResolver = null にして外部参照を禁じる

を必ず行うこと。

  • SAMLTool.com(エンコード・デコード、署名・検証)

NameIDFormat

SAML Assertions に指定する(定義は SAML Core)。

Format 汎用認証サイトでの位置づけ
unspecified 既定値(ユーザ名)
entity UserId(Public UserID)
emailAddress メアド
persistent UserId(Pairwise UserID)

補足(NameID の選び方): これは後から変えられない設計判断である。

選択 長所 短所
emailAddress SP 側で人間が識別しやすい メアド変更で別人になる/SP に個人情報が渡る
persistent(Pairwise) SP をまたいだ名寄せを防げる SP 側で誰か分からない(属性で補う必要)
内部の不変 ID 変更に強い 可読性が低い

実務では persistent(または不変 ID)を NameID にし、
表示名やメアドは <AttributeStatement> で別途渡す
のが望ましい。
メアドを主キーにすると、姓の変更・部署異動で必ず破綻する

AttributeValue

  • 様々なクレームを AttributeStatement > Attribute > AttributeValue に指定できる。
  • AD FS の実装例
    この設定は、Custom Claim Rule(要求規則)に設定するらしい
    <samlp:AuthnRequest> に要求を設定するという方式では無さそう)。

補足: AD FS / Microsoft Entra ID では、
どの属性を渡すかは IdP 側の構成(要求規則 / 要求のマッピング)で決まる
SP から <AttributeConsumingServiceIndex> で要求できる建前だが、
IdP はこれを無視してよいSAML Profiles)ため、
実質的に IdP 管理者との事前合意が必要になる。

登録情報

IdP/STS側

  • 新規追加

    • NameIDFormat
    • SAMLResponse エンドポイント
      Assertion Consumer Service とも呼ばれ、
      OAuth のコンテキストでは redirect_uri に相当する。
  • OAuth2 / OIDC インフラを流用

    • Issuer(client_id を流用)
    • Client の公開鍵

Client側

  • 新規追加 — なし
  • OAuth2 / OIDC インフラを流用 — IdP の公開鍵
  • 対応不要(Client が個別に実装すればイイ)
    • SAMLRequest エンドポイント
    • IdP のログアウト用 URL

シーケンス(Artifact無し)

Technical Overview から取り出したサンプル・シーケンス

リクエスト

  • HTTP Redirect Binding の場合
HTTP/1.1 302 Object Moved
Date: 21 Jan 2004 07:00:49 GMT
Location: https://idp.example.org/SAML2/SSO/Redirect?SAMLRequest=request&RelayState=...
Content-Type: text/html; charset=iso-8859-1
GET /SAML2/SSO/Redirect?SAMLRequest=request&RelayState=... HTTP/1.1
Host: idp.example.org
  • SAMLRequest=request
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="identifier_1" Version="2.0"
  IssueInstant="2004-12-05T09:21:59Z" AssertionConsumerServiceIndex="1">
  <saml:Issuer>
    https://sp.example.com/SAML2
  </saml:Issuer>
  <samlp:NameIDPolicy AllowCreate="true"
    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
</samlp:AuthnRequest>
  • RelayState=value
    SAML では、
    • OAuth / OpenID Connectと違って、Resource Server をサポートしない。
    • 故に、SP 上のリソースを取得するので、そのリソース入手先の URI を指定する。

レスポンス

  • HTTP POST Binding の場合
<form method="post" action="https://sp.example.com/SAML2/SSO/POST" ...>
  <input type="hidden" name="SAMLResponse" value="response" />
  <input type="hidden" name="RelayState" value="value" />
  <input type="submit" value="Submit" />
</form>
POST /SAML2/SSO/POST HTTP/1.1
Host: sp.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: nnnn

SAMLResponse=response&RelayState=value

アサーション

<samlp:Response
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="identifier_2" InResponseTo="identifier_1" Version="2.0"
  IssueInstant="2004-12-05T09:22:05Z"
  Destination="https://sp.example.com/SAML2/SSO/POST">

  <saml:Issuer>
    https://idp.example.org/SAML2
  </saml:Issuer>

  <samlp:Status>
    <samlp:StatusCode
      Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>

  <saml:Assertion
    ID="identifier_3" Version="2.0" IssueInstant="2004-12-05T09:22:05Z">

    <saml:Issuer>
      https://idp.example.org/SAML2
    </saml:Issuer>

    <!-- a POSTed assertion MUST be signed -->
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      ...
    </ds:Signature>

    <saml:Subject>
      <saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
        3f7b3dcf-1674-4ecd-92c8-1544f346baf8
      </saml:NameID>
      <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
          InResponseTo="identifier_1"
          NotOnOrAfter="2004-12-05T09:27:05Z"
          Recipient="https://sp.example.com/SAML2/SSO/POST"/>
      </saml:SubjectConfirmation>
    </saml:Subject>

    <saml:Conditions
      NotBefore="2004-12-05T09:17:05Z"
      NotOnOrAfter="2004-12-05T09:27:05Z">
      <saml:AudienceRestriction>
        <saml:Audience>
          https://sp.example.com/SAML2
        </saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>

    <saml:AuthnStatement
      AuthnInstant="2004-12-05T09:22:00Z" SessionIndex="identifier_3">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
        </saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
  </saml:Assertion>
</samlp:Response>

シーケンス(Artifact有り)

医療分野共通認証基盤整備コンソーシアムのドキュメントから取り出したサンプル・シーケンス

リクエスト1

  • HTTP Redirect Binding の場合
HTTP/1.1 302 Object Moved
Location: https://auth.pki.med.or.jp/sso/SSORedirect/metaAlias/idp?SAMLRequest=...
  • HTTP POST Binding の場合(自動 POST FORM)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
  <body onload="document.forms[0].submit()">
    <noscript>
      <p>
        <strong>Note:</strong>
        Since your browser does not support JavaScript, you must press the Continue
        button once to proceed.
      </p>
    </noscript>
    <form action="https://ServiceProvider.com/SAML/SLO/Browser" method="post">
      <div>
        <input type="hidden" name="RelayState"
          value="0043bfc1bc45110dae17004005b13a2b"/>
        <input type="hidden" name="SAMLRequest"
          value="PHNhbWxwOkxvZ291dFJlcXVlc3QgeG1sbnM6c2FtbHA9InVybjpvYX ..."/>
      </div>
      <noscript>
        <div>
          <input type="submit" value="Continue"/>
        </div>
      </noscript>
    </form>
  </body>
</html>
  • SAMLRequest
<samlp:AuthnRequest
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  ID="s2cee63177ff33bcacd5263caa4af9da101837de24"
  Version="2.0"
  IssueInstant="2011-06-28T05:08:41Z"
  Destination="https://auth.pki.med.or.jp/sso/SSORedirect/metaAlias/idp"
  ForceAuthn="false" IsPassive="false"
  ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
  AssertionConsumerServiceURL="https://ServiceProvider.com/SAML/Consumer/metaAlias/sp">
  <saml:Issuer
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    sso-sp
  </saml:Issuer>
  <samlp:NameIDPolicy
    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
    SPNameQualifier="openam-sp" AllowCreate="true">
  </samlp:NameIDPolicy>
  <samlp:RequestedAuthnContext Comparison="exact">
    <saml:AuthnContextClassRef xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
      urn:oasis:names:tc:SAML:2.0:ac:classes:X509
    </saml:AuthnContextClassRef>
  </samlp:RequestedAuthnContext>
</samlp:AuthnRequest>

レスポンス1

  • HTTP Redirect Binding の場合
HTTP/1.1 302 Object Moved
Location: https://ServiceProvider.com/SAML/SLO/Browser?SAMLart=...&RelayState=...
  • HTTP POST Binding の場合
    • 仕様中に、form も使用可能とある(≒ HTTP POST Binding 使用可能)。
    • が、記載無し … SAMLart 送付に POST 不要と言う判断なのだろう。

リクエスト2(Artifact Binding)

POST /SAML/Artifact/Resolve HTTP/1.1
HOST: IdentityProvider.com
Content-Type: text/xml
Content-Length: nnn
SOAPAction: http://www.oasis-open.org/committees/security

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <samlp:ArtifactResolve xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
      xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ID="_6c3a4f8b9c2d" Version="2.0"
      IssueInstant="2004-01-21T19:00:49Z">
      <Issuer>https://ServiceProvider.com/SAML/</Issuer>
      <ds:Signature
        xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...Signature...</ds:Signature>
      <Artifact>...artifact...</Artifact>
    </samlp:ArtifactResolve>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

レスポンス2(Artifact Binding)

HTTP/1.1 200 OK
Content-Type: application/soap+xml
Content-Length: nnnn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
  <SOAP-ENV:Body>
    <samlp:ArtifactResponse
      xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
      ID="identifier_3" InResponseTo="identifier_2" Version="2.0"
      IssueInstant="2004-12-05T09:22:05Z">
      <samlp:Status>
        <samlp:StatusCode
          Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
      </samlp:Status>
      <samlp:Response
        xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
        ID="identifier_4" InResponseTo="identifier_1"
        Version="2.0" IssueInstant="2004-12-05T09:22:05Z"
        Destination="https://ServiceProvider.com/SAML/SSO/Artifact">
        <saml:Issuer>
          medical-doctor-sso-idp
        </saml:Issuer>
        <samlp:Status>
          <samlp:StatusCode
            Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
        </samlp:Status>
        <saml:Assertion>...</saml:Assertion>
      </samlp:Response>
    </samlp:ArtifactResponse>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

アサーション

<saml:Assertion
 xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
 Version="2.0" ID="s2ead62f287a9e36c31b3a693974582fdae20c5351"
 IssueInstant="2011-06-28T05:08:41Z">
 <saml:Issuer>
   medical-doctor-sso-idp
 </saml:Issuer>
 <saml:Subject>
   <saml:NameID
     NameQualifier="medical-doctor-sso-idp"
     SPNameQualifier="sso-sp"
     Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">
     1234567890
   </saml:NameID>
   <saml:SubjectConfirmation
     Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
     <saml:SubjectConfirmationData
       InResponseTo="s2cee63177ff33bcacd5263caa4af9da101837de24"
       NotOnOrAfter="2011-06-28T05:18:41Z"
       Recipient="https://ServiceProvider.com/SAML/Consumer/metaAlias/sp">
     </saml:SubjectConfirmationData>
   </saml:SubjectConfirmation>
 </saml:Subject>
 <saml:Conditions
   NotBefore="2011-06-28T04:58:41Z"
   NotOnOrAfter="2011-06-28T05:18:41Z">
   <saml:AudienceRestriction>
     <saml:Audience>
       sso-sp
     </saml:Audience>
   </saml:AudienceRestriction>
 </saml:Conditions>
 <saml:AuthnStatement
   AuthnInstant="2011-06-28T05:08:41Z" SessionIndex="xxxxx">
   <saml:AuthnContext>
     <saml:AuthnContextClassRef>
       urn:oasis:names:tc:SAML:2.0:ac:classes:X509
     </saml:AuthnContextClassRef>
   </saml:AuthnContext>
 </saml:AuthnStatement>
 <saml:AttributeStatement>
   <saml:Attribute Name="HCROLE">
     <saml:AttributeValue
       xmlns:xs="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:type="xs:string">
       Medical Doctor
     </saml:AttributeValue>
   </saml:Attribute>
 </saml:AttributeStatement>
</saml:Assertion>

シーケンス(Microsoft Entra ID)

リクエスト

<samlp:AuthnRequest
  xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
  ID="id6c1c178c166d486687be4aaf5e482730"
  Version="2.0" IssueInstant="2013-03-18T03:28:54.1839884Z"
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://www.contoso.com</Issuer>
  <NameIDPolicy
    Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent"/>
</samlp:AuthnRequest>

レスポンス

<samlp:Response
  ID="_a4958bfd-e107-4e67-b06d-0d85ade2e76a"
  Version="2.0" IssueInstant="2013-03-18T07:38:15.144Z"
  Destination="https://contoso.com/identity/inboundsso.aspx"
  InResponseTo="id758d0ef385634593a77bdf7e632984b6"
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
    https://login.microsoftonline.com/82869000-6ad1-48f0-8171-272ed18796e9/
  </Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    ...
  </ds:Signature>
  <samlp:Status>
    <samlp:StatusCode
      Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
  </samlp:Status>
  <Assertion>...</Assertion>
</samlp:Response>

アサーション

<Assertion
  ID="_bf9c623d-cc20-407a-9a59-c2d0aee84d12"
  Version="2.0" IssueInstant="2013-03-18T07:38:15.144Z"
  xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
  <Issuer>https://login.microsoftonline.com/82869000-6ad1-48f0-8171-272ed18796e9/</Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    ...
  </ds:Signature>
  <Subject>
    <NameID>Uz2Pqz1X7pxe4XLWxV9KJQ+n59d573SepSAkuYKSde8=</NameID>
    <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <SubjectConfirmationData
        InResponseTo="id758d0ef385634593a77bdf7e632984b6"
        NotOnOrAfter="2013-03-18T07:43:15.144Z"
        Recipient="https://contoso.com/identity/inboundsso.aspx" />
    </SubjectConfirmation>
  </Subject>
  <Conditions
    NotBefore="2013-03-18T07:38:15.128Z"
    NotOnOrAfter="2013-03-18T08:48:15.128Z">
    <AudienceRestriction>
      <Audience>https://www.contoso.com</Audience>
    </AudienceRestriction>
  </Conditions>
  <AttributeStatement>
    <Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name">
      <AttributeValue>testuser@contoso.com</AttributeValue>
    </Attribute>
    <Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
      <AttributeValue>3F2504E0-4F89-11D3-9A0C-0305E82C3301</AttributeValue>
    </Attribute>
  </AttributeStatement>
  <AuthnStatement
    AuthnInstant="2013-03-18T07:33:56.000Z"
    SessionIndex="_bf9c623d-cc20-407a-9a59-c2d0aee84d12">
    <AuthnContext>
      <AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:Password
      </AuthnContextClassRef>
    </AuthnContext>
  </AuthnStatement>
</Assertion>

移行メモ: 元の掲載では <ds:Signature> の名前空間が
https://www.w3.org/2000/09/xmldsig#(https)になっていたが、
XML の名前空間 URI は文字列として厳密に一致する必要があり、
正しくは http://www.w3.org/2000/09/xmldsig#(http)である。
ここを間違えると署名検証が通らない。

補足: この例の <NameID>
Uz2Pqz1X7pxe4XLWxV9KJQ+n59d573SepSAkuYKSde8= という
ハッシュ値になっているのは persistent(Pairwise)指定のため。
SP ごとに異なる値になり、SP をまたいだ名寄せができない
PPIDを参照)。

シーケンス(Cybozu)

リクエスト

<samlp:AuthnRequest
  AssertionConsumerServiceURL="https://(sub_domain).cybozu.com/saml/acs"
  ID="szqd0c3d0u3vpz5jwna4p24iso42opc4"
  IssueInstant="2013-04-01T00:00:00Z"
  ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
  Version="2.0" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
  <saml:Issuer
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    https://(sub_domain).cybozu.com
  </saml:Issuer>
  <samlp:NameIDPolicy
    Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" />
</samlp:AuthnRequest>

レスポンス

<samlp:Response
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  ID="s2b39863179da0358f10bb499d6ac0e64062e89e1d"
  InResponseTo="szqd0c3d0u3vpz5jwna4p24iso42opc4"
  Version="2.0" IssueInstant="2013-04-01T00:30:00Z"
  Destination="https://(sub_domain).cybozu.com/saml/acs">
  <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
    https://(idp_host)
  </saml:Issuer>
  <samlp:Status>
    <samlp:StatusCode
      Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>
  <saml:Assertion>...</saml:Assertion>
</samlp:Response>

アサーション

<saml:Assertion
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="s2822cac48e7b7ec82ff36710996423e7baec43a00"
  IssueInstant="2013-04-01T00:30:00Z" Version="2.0">
  <saml:Issuer>
    https://(idp_host)
  </saml:Issuer>
  <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
    <!-- アサーションの署名の内容 -->
  </ds:Signature>
  <saml:Subject>
    <saml:NameID
      NameQualifier="https://(idp_host)"
      SPNameQualifier="https://(sub_domain).cybozu.com"
      Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">
      watanabe
    </saml:NameID>
    <saml:SubjectConfirmation
      Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
      <saml:SubjectConfirmationData
        InResponseTo="szqd0c3d0u3vpz5jwna4p24iso42opc4"
        NotOnOrAfter="2013-04-01T00:40:00Z"
        Recipient="https://(sub_domain).cybozu.com/saml/acs" />
    </saml:SubjectConfirmation>
  </saml:Subject>
  <saml:Conditions
    NotBefore="2013-04-01T00:20:00Z"
    NotOnOrAfter="2013-04-01T00:40:00Z">
    <saml:AudienceRestriction>
      <saml:Audience>https://(sub_domain).cybozu.com</saml:Audience>
    </saml:AudienceRestriction>
  </saml:Conditions>
  <saml:AuthnStatement
    AuthnInstant="2013-04-01T00:29:30Z"
    SessionIndex="s2901e6c0e0cc0c8f1aa1075215125b2676774dd01">
    <saml:AuthnContext>
      <saml:AuthnContextClassRef>
        urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
      </saml:AuthnContextClassRef>
    </saml:AuthnContext>
  </saml:AuthnStatement>
</saml:Assertion>

移行メモ: 元の掲載では <saml:Audience> の閉じタグが欠落していたので補った。

参考

IdM実験室

OneLogin

developers.onelogin.com

SAMLTool.com

分類 ツール
X.509 CERTS Obtain Self-Signed Certs / Calculate Fingerprint / Format X.509 Certificate / Format Private Key
CODE / DECODE Base64 / Gzip / URL Encode・Decode / Deflate + Base64 Encode / Base64 Decode + Inflate
ENCRYPT / DECRYPT Encrypt XML / Decrypt XML
SIGN AuthNRequest / Response / Logout Request / Logout Response / Metadata
VALIDATE XML Against XSD Schema / SAML AuthN Request / SAML Response / SAML Logout Request / SAML Logout Response
その他 ATTRIBUTE EXTRACTOR / XML PRETTY PRINT / BUILD METADATA / EXAMPLES / EXTERNAL SAML TOOLS

補足(取り扱い注意): これらのオンライン ツールに
本番の秘密鍵や実際のアサーションを貼り付けてはならない
検証は自己署名証明書とダミーのクレームで行うこと。

Sustainsys/Saml2

GitHub

NuGet

SignedXmlでの署名・検証の問題対策

補足(自前実装を勧めない): ここまで見た通り、SAML の SP 実装には
署名検証・XSW 対策・リプレイ防止・時刻ずれ・エンコードの罠が集中する。
一つでも落とすと認証を回避されるため、
実績のあるライブラリを使うのが正解である。

環境 選択肢
ASP.NET Core Sustainsys.Saml2 / ITfoxtec.Identity.Saml2
Java OpenSAML / Spring Security SAML
共通 IdP 側(AD FS / Entra ID)に寄せて、アプリは OIDC で受ける

最後の選択肢が実務では最も堅い。
Microsoft Entra ID を挟めば、
SP 側は OpenID Connect だけを扱えばよくなる。


Tags: 移行, IT国際標準, 認証基盤, クレームベース認証, SAML

NetDevInfraWiki

マイクロソフト系技術情報 Wiki
Open 棟梁 Wiki

(未着手)

開発基盤部会 Wiki

移行管理: DONETODO

Clone this wiki locally