-
Notifications
You must be signed in to change notification settings - Fork 0
MS_SAMLImplementation
- 戻る(SAMLの仕様を読む。)
- SAMLを実装する。
- SAMLの実装を検証する。
- 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>(認証強度の指定)を改ざんされ、
強い認証を要求したつもりが弱い認証で通る後者があるため、高保証が要る系では要求も署名するのが正しい。
SAML は、Web Browser SSO Profile 以外で使われていない。
IdM 実験室によると…。
昔はデータサイズの問題で、Artifact Binding を使っていたが
最近は通信速度や端末性能の向上により、使われることは減ってきているらしい。
SP と IdP の間で直接通信をさせなければならないので、
クラウドサービスと社内 IdP を連携させる場合など面倒なため。
ただ、今でも稀に Artifact Binding しか使えない SP が
存在するので、仕方なく(IdP 側に)実装することがある。
なお、AD FS では Artifact Binding をサポートしている。
Microsoft Entra ID は POST Binding のみで
Artifact Binding は使えない。
-
<NameIDPolicy>Format
| 採否 | Format | 内容 |
|---|---|---|
| ○ | unspecified |
既定値(ユーザ名) |
| ○ | entity |
Public UserID |
| ○ | emailAddress |
メアド |
| ○ | persistent |
永続的仮名 - Pairwise UserID |
| ☓ | transient |
一時的仮名 |
| ☓☓ | X509SubjectName |
|
| ☓☓ | WindowsDomainQualifiedName |
|
| ☓☓ | kerberos |
-
AllowCreate="false" -
<RequestedAuthnContext>(多くの SP では使用していない)-
Comparison属性 — ○exact/ ☓minimum/ ☓maximum/ ☓better -
<AuthnContextClassRef>要素
(urn:oasis:names:tc:SAML:2.0:ac:classes)
-
| 採否 | Class |
|---|---|
| ○ |
unspecified(不特定の方法で) |
| ○ |
PasswordProtectedTransport(パスワード) |
| ☓ |
Password(PasswordProtectedTransport との違いが?) |
| ☓ |
PreviousSession(既存のセッションを利用) |
| ☓ |
X509 / SecureRemotePassword / TimeSyncToken
|
| ☓ |
Smartcard / SmartcardPKI / SoftwarePKI
|
| ☓ |
MobileOneFactorContract / MobileOneFactorUnregistered
|
| ☓ |
MobileTwoFactorContract / MobileTwoFactorUnregistered
|
| ☓ |
PGP / SPKI / XMLDSig / TLSClient / Kerberos
|
| ☓ |
Telephony / NomadTelephony / AuthenticatedTelephony / PersonalTelephony
|
| ☓ |
InternetProtocol / InternetProtocolPassword
|
補足(
PasswordとPasswordProtectedTransportの違い): 元ページの
「違いが?」という疑問に答えておく。
Class 意味 Passwordパスワードを提示して認証した。通信路の保護は問わない PasswordProtectedTransportパスワードを提示して認証し、かつ通信路が保護されている(HTTPS) つまり「HTTPS だったかどうか」の一点である。
現在はすべて HTTPS なので、実質的に
PasswordProtectedTransportを使えばよい。
例を参考にする(後述)。
samltool.io からアサーションを取得し、SAMLTool.com で確認するなど。
SignedXml クラスの実装に問題があり、
ネストした複数の XML 署名が Verify できないという問題があるらしい。
- SAML は Assertion 側と Response 側どちらでの
<Signature>の存在を
仕様上は認めている。 - そのため IdP(またはその設定)によっては両方に XML 署名が存在する場合がある。
- ネストしている場合の正規化では、外側の
<Signature>を取り除き、内部は残しておく。 -
System.Security.Cryptography.SignedXmlクラスの実装に問題がある。
(ネストした<Signature>を扱うと Verify できないバグがある)
※ 外側だけ検証すればイイ気がする。
後述が、SignedXml によるネストした XML 署名・検証結果。
補足(「外側だけ検証すればイイ」の是非): 結論から言うと、
「外側だけ」で構わないが、条件がある。
<Response>の署名を検証したなら、
その署名対象ノードの配下にある<Assertion>を使う限り、
内側の署名を重ねて検証する必要はない(完全性は担保されている)。ただし、次をやってはならない。
- 文書全体を XPath で再検索して
<Assertion>を取り出す
(署名対象外の<Assertion>を掴む= XSW)- 「どちらか署名があればよい」として、署名の有無を確認せずに
内側を読む実装上は「署名検証に成功したノードのオブジェクト参照を保持し、
そこからしか値を読まない」という形にするのが確実である。
XmlDocument を利用する際に PreserveWhitespace を単に true にする。
補足:
XmlDocumentの既定(PreserveWhitespace = false)では
意味を持たない空白が捨てられるため、
受信したバイト列と正規化結果が食い違い、ダイジェストが一致しなくなる。同様の理由で、受信した XML を整形(pretty print)してはならない。
ログ出力のために整形したものを検証に回す、という事故が起きやすい。
以下を用いて、SAML を検証する。
-
SAML v2 schema - OASIS
しかし、結局 XPath で検証していたりする。
(適切な、ミニマムの XSD をオンラインで発見できないため)
補足(スキーマ検証は「あった方がよい」): スキーマ検証は
XSW 攻撃に対する多層防御の一つになる
(IDの重複や不正な位置の要素を弾ける)。ただし .NET でスキーマ検証を行う際は、
- XSD をオンラインで取りに行かない(ローカルに同梱する)
- DTD 処理を無効化する(
DtdProcessing = Prohibit。XXE 対策)XmlResolver = nullにして外部参照を禁じるを必ず行うこと。
- SAMLTool.com(エンコード・デコード、署名・検証)
SAML Assertions に指定する(定義は SAML Core)。
| Format | 汎用認証サイトでの位置づけ |
|---|---|
unspecified |
既定値(ユーザ名) |
entity |
UserId(Public UserID) |
emailAddress |
メアド |
persistent |
UserId(Pairwise UserID) |
補足(
NameIDの選び方): これは後から変えられない設計判断である。
選択 長所 短所 emailAddressSP 側で人間が識別しやすい メアド変更で別人になる/SP に個人情報が渡る persistent(Pairwise)SP をまたいだ名寄せを防げる SP 側で誰か分からない(属性で補う必要) 内部の不変 ID 変更に強い 可読性が低い 実務では
persistent(または不変 ID)をNameIDにし、
表示名やメアドは<AttributeStatement>で別途渡すのが望ましい。
メアドを主キーにすると、姓の変更・部署異動で必ず破綻する。
- 様々なクレームを
AttributeStatement > Attribute > AttributeValueに指定できる。 -
AD FS の実装例
この設定は、Custom Claim Rule(要求規則)に設定するらしい
(<samlp:AuthnRequest>に要求を設定するという方式では無さそう)。
補足: AD FS / Microsoft Entra ID では、
どの属性を渡すかは IdP 側の構成(要求規則 / 要求のマッピング)で決まる。
SP から<AttributeConsumingServiceIndex>で要求できる建前だが、
IdP はこれを無視してよい(SAML Profiles)ため、
実質的に IdP 管理者との事前合意が必要になる。
-
新規追加
NameIDFormat- SAMLResponse エンドポイント
Assertion Consumer Service とも呼ばれ、
OAuth のコンテキストではredirect_uriに相当する。
-
OAuth2 / OIDC インフラを流用
- Issuer(
client_idを流用) - Client の公開鍵
- Issuer(
- 新規追加 — なし
- OAuth2 / OIDC インフラを流用 — IdP の公開鍵
- 対応不要(Client が個別に実装すればイイ)
- SAMLRequest エンドポイント
- IdP のログアウト用 URL
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>医療分野共通認証基盤整備コンソーシアムのドキュメントから取り出したサンプル・シーケンス
- 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>- 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 不要と言う判断なのだろう。
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>
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><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を参照)。
<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>の閉じタグが欠落していたので補った。
-
SAML認証に関する自分なりのまとめ - なんとな~くしあわせ?の日記
https://nantonaku-shiawase.hatenablog.com/entry/2016/07/13/081053 -
SAML2.0でのシングルサインオン実装と戦うあなたに(.NET編) - Qiita
https://qiita.com/ea54595/items/e932644477a45070690b -
SAML Token - samltool.io
https://samltool.io/
-
Azure ADのSAML対応(IdP編)
https://idmlab.eidentity.jp/2014/08/azure-adsamlidp.html -
続Azure ADのSAML対応(IdP編)~SP Initiatedに対応
https://idmlab.eidentity.jp/2014/09/azure-adsamlidpsp-initiated.html -
[Office365/AzureAD]OpenAMとのID連携
-
[AzureAD/Java]WebLogic/JavaアプリケーションとAzure ADを連携する
https://idmlab.eidentity.jp/2016/01/azureadjavaweblogicjavaazure-ad.html -
- [AD FS]プライバシーに考慮したID連携設定
https://idmlab.eidentity.jp/2014/12/ad-fsid.html - JavaアプリSPとSAML Artifact Bindingで連携する際の証明書ストア
https://idmlab.eidentity.jp/2016/01/ad-fsjavaspsaml-artifact-binding.html
- [AD FS]プライバシーに考慮したID連携設定
-
C# & ASP.NET SAML Authentication Examples
https://developers.onelogin.com/saml/c-and-aspnet -
onelogin/dotnet-saml: SAML toolkit for .NET
https://github.com/onelogin/dotnet-saml
-
SAML Testing Tools | Online SAML Debugger | Examples
https://www.samltool.com/index.php -
ONLINE TOOLS
https://www.samltool.com/online_tools.php
| 分類 | ツール |
|---|---|
| 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 |
補足(取り扱い注意): これらのオンライン ツールに
本番の秘密鍵や実際のアサーションを貼り付けてはならない。
検証は自己署名証明書とダミーのクレームで行うこと。
-
SignedXml fails to validate second signature in same document · Issue #32526 · dotnet/corefx
https://github.com/dotnet/corefx/issues/32526 -
Sustainsys/Saml2: Saml2 Authentication services for ASP.NET
https://github.com/Sustainsys/Saml2
- NuGet Gallery | Sustainsys.Saml2
https://www.nuget.org/packages/Sustainsys.Saml2/
-
SignedXml には前述の問題がある。
-
https://gist.github.com/daisukenishino2/69074e571cf89c23cce6d1522abc67e5
- ネストした XML の署名・検証処理の問題の確認用コード。
- 使用する範囲(SAML2 Assertion の署名・検証)で、問題なく動作するものと思われる。
-
OpenTouryo/SignedXml2.cs at develop · OpenTouryoProject/OpenTouryo
https://github.com/OpenTouryoProject/OpenTouryo/blob/develop/root/programs/CS/Frameworks/Infrastructure/Public/Security/Xml/SignedXml2.cs- 上記の gist.github.com の内容でライブラリ化した。
- 汎用認証サイトの SAML 対応で使っている。
補足(自前実装を勧めない): ここまで見た通り、SAML の SP 実装には
署名検証・XSW 対策・リプレイ防止・時刻ずれ・エンコードの罠が集中する。
一つでも落とすと認証を回避されるため、
実績のあるライブラリを使うのが正解である。
環境 選択肢 ASP.NET Core Sustainsys.Saml2/ITfoxtec.Identity.Saml2Java OpenSAML / Spring Security SAML 共通 IdP 側(AD FS / Entra ID)に寄せて、アプリは OIDC で受ける 最後の選択肢が実務では最も堅い。
Microsoft Entra ID を挟めば、
SP 側は OpenID Connect だけを扱えばよくなる。
Tags: 移行, IT国際標準, 認証基盤, クレームベース認証, SAML
このWikiは「Open棟梁Project」,「OSSコンソーシアム 開発基盤部会」によって運営されています。