自己紹介
現在はエンジニアとして株式会社メタップスでSaaS一元管理ツールであるメタップスクラウドの開発やSAMLを使ったSSOを簡単に導入できるようなライブラリ(sp-rails-saml)の開発を行っています。
その中でSAMLに関して勉強を行ったので、現状理解している範囲でSAMLの仕組みや、SAMLを使って認証を行う際のフローを関してまとめてみました。
特にSAMLによってやりとりされるSAML ResponseやSAML Requestの中身に関して結構複雑だと思ったので、そこに重点を置いて書いてみました。
SAMLとは
SAMLは「Security Assertion Markup Language」のことで、セキュリティ情報の伝達を目的としてOASISが定義したXMLベースの認証情報の仕様のことです。
電子署名を行うことで異なるインターネットドメイン間でも信頼できる情報のやりとりを行うことができます。
現在は2005年に定義されたSAML2.0が最新版として使用されています。
認証情報を管理するサービスにログインすることで連携されているSaaSへのシングルサインオンを実現する際にSAMLが使われたりします。
SAMLに関してよく出てくる用語
- SSO
- シングルサインオンの略で、一つのサーバーにログインすることで連携されているサービス全てへのログインが行える様な仕組みのことを指します。
- SLO
- シングルログアウトの略で、一つのサーバーをログアウトすることで連携されている全てのサービスからログアウトが行えるような仕組みのことを指します。
- プロビジョニング
- シングルサインオン時にユーザーがいなければユーザーを作成する様な機能のことを指します。
- SP
- Service Providerの略でシングルサインオンを使用してログインしたいSaaSのことを指します。
- 例: Slack、SmartHR、Zoom
- IdP
- Identity Providerの略で認証情報を管理するサービスのことを指します。
- 例: メタップスクラウド
- アサーション
- SAMLにおけるシングルサインオンを行う際の認証情報の部分のことです。
- Metadata
- SAML連携において必要になるSP、IdPそれぞれの情報をまとめたデータになります。
- 例外があるかもしれないですが、ほとんどの場合IdP側、SP側それぞれがMetadataを返すURLを用意しています。
SAMLを使ったSSOの流れ
SAMLを使ったSSOの流れですが、「SP Initiated」というSPが起点となる場合と「IdP Initiated」というIdPが起点となる場合の2つがあります。
SP Initiatedの場合
SP InitiatedはSPが起点となったSAML認証フローになり
1. SPのログイン画面を表示し、ログインボタンを押す
2. SPが「SAML Request」という認証リクエストをIdPに送る
3. IdPは受け取ったSAML RequestとIdPにログインしているユーザー情報を元に「SAML Response」を生成しSPに送る
4. SPは受け取ったSAML Responseを検証し、問題なければSAML Responseに記載されているユーザー情報を元にログイン処理を行う
この様な順番で処理が行われます。
図に表すと下記の様な形になり、ブラウザを通してSAML RequestとSAML Responseのやりとりが行われる形になります。
IdP Initiatedの場合
IdP InitiatedはIdPが起点となったSAML認証フローになり
1. IdPの画面に用意されているSPへのログインボタンを押す
2. ログインしているユーザー情報を元にSAML Responseを生成してSPに送る
3. SPは受け取ったSAML Responseを検証し、問題なければSAML Responseに記載されているユーザー情報を元にログイン処理を行う
この様な順番で処理が行われます。
図に表すと下記の様な形になり、ブラウザを通してIdPからSPへSAML Responseが送られます。
SP Initiatedと異なり、SPがSAML RequestをIdPに送るフローが省かれる形になります。
IdP Initiatedの場合、SP側から送られてくるSAML Requestが無いので、SP側でどのような方法でSAML Responseを返して欲しいかわかりません。
そのため、IdP Initiatedを行う場合はSP側へどの様な形でSAML Responseを返すのかの情報を持っておく必要があります。
SSOにてやりとりされるSAMLのデータの中身
SSOの中でSAML RequestとSAML Responseというデータのやりとりが行われ、それを元にSSOを行うのですがこのSAML RequestとSAML Responseの中身に関して解説した記事が少ないので現状理解している範囲で説明したいと思います。
SAML Request
SAML Requestのデータは下記の様な形で渡されることになります。
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="Request ID" Version="2.0" IssueInstant="2004-12-05T09:21:59Z" AssertionConsumerServiceIndex="1">
<saml:Issuer>https://sp.saas.com</saml:Issuer>
<samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" />
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
</samlp:AuthnRequest>
上記の場合はAuthnRequest、Issuer、NameIDPolicy、RequestedAuthnContextタグによって構成されていますが、署名が行われる場合は上記の加えてSignature
タグが追加されることになります。
それぞれのタグの役割と中身に関しては下記の様な形になります。
AuthnRequest
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="Request ID" Version="2.0" IssueInstant="2004-12-05T09:21:59Z" AssertionConsumerServiceIndex="1">
タグ/属性 | 内容 | 備考 |
---|---|---|
ID | RequestのユニークなIDが振られます。 | SAML ResponseのInResponseTo属性にこのIDが振られる形になります。 SAML Requestに対するSAML Responseであるかを検証したい場合はここを見る形になると思います。 |
Version | SAMLはバージョンが入ります | |
AssertionConsumerServiceURL | SP側でSAML Responseを受けるURLになります | |
ProtocolBinding | SAML Responseの受ける際の方法が記載されています。 | HTTP POSTにてSAML Responseを受け取る場合やメッセージングプロトコルであるSOAPを使用する場合があります。 |
Destination | SAML Requestを送る先のURLが入ります | |
IssueInstant | SAML Requestが作られたタイミングの時刻が入ります。 |
Issuer
<saml:Issuer>https://sp.saas.com/</saml:Issuer>
タグ/属性 | 内容 | 備考 |
---|---|---|
Issuer | URL形式のSP側のIDみたいなもの? | 基本的にmetadataを返すURLが設定されます。 SAML RequestのIssuerはSP側のEntityIDが設定され、 SAML ResponseのIssuerにはIdP側のEntityIDが設定される形になります。 |
NameIDPolicy
<samlp:NameIDPolicy AllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient" />
タグ/属性 | 内容 | 備考 |
---|---|---|
Format | SAML Responseで受け取るNameIDの形式が入ります。 |
RequestedAuthnContext
<samlp:RequestedAuthnContext Comparison="exact">
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
</samlp:RequestedAuthnContext>
タグ/属性 | 内容 | 備考 |
---|---|---|
RequestedAuthnContext/Comparison | AuthnContextClassRefの比較方法になります。 | exactの場合は完全一致するものを検索する形になります。 |
AuthnContextClassRef | IdP側でユーザーをどのように認証して欲しいかを指定 | ID/Passwordでの認証を行って欲しい場合はurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport を指定する形になります。 |
SAML Response
SAML Responseのデータは下記の様な形で渡されることになります。
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfx64932347-5370-fea3-61bf-486ba519c861" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="https://sp.saas.com/acs" InResponseTo="4fee3b046395c4e751011e97f8900b5273d56685">
<saml:Issuer>http://idp.service.com/metadata</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<ds:Reference URI="#pfx64932347-5370-fea3-61bf-486ba519c861">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>VzCbNSpQ/1iWYTsFTlWMbB9xx+ttOuIuEOpiBBOlc9I=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>HzmAJ2vPPjUHSXJczYb6Tnjx2iVwcmYc0TdyrSeoXYbMx17XEzzFRnSx03nZEsc+xbHmN++Mal7CwwvlFkSp9js3ubFRtLP70cJ7zNaQA83gfhHPu5roFRHqGoyKFvkJVIWnjnYrL/fvOUNC0osFNll85a6geoJGvTrNK9i+LEQtkATuO5/NqrtqPLqFzJc3veC74ChreMXd/1FBZO8w6nfo74yPDk9taJ/QFYRQikZd4PhMtx0PtArsVmpoYZWjJ5Drcdyq1ELshWRQrVXgsY/iGwo8CAhTumWB89SnuraUR23B4vD4wiapsoQzzun6hXiTXgRo4nDrPlUXvM/v3A==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIDVjCCAj6gAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MRQwEgYDVQQKDAttZXRhcHMgSW5jLjETMBEGA1UECwwKbWNsb3VkIElkcDEPMA0GA1UEAwwGbWNsb3VkMB4XDTIxMDUyMTA1MjQyMVoXDTI2MDUyMDA1MjQyMVowPDEUMBIGA1UECgwLbWV0YXBzIEluYy4xEzARBgNVBAsMCm1jbG91ZCBJZHAxDzANBgNVBAMMBm1jbG91ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOldNOBsT8bmI3CqSIy3E76+6o4jFBSjRlU4DXLKKLUvTdqAl82FBCZFlSKOn6zNmihKpRwn0loHED+2Trla5zLN++vgRm5WsMkQBUqv/uq20EB0LG4cosMYseDABUrAy6PF+RkNlLDDViw7/POFgQgtRomNm6Yn3nUEmh6QHlX25A83nvUwUzV5R76jVe6elRXviB/U7WkzZHJVeLUTaxFMK/u0xL7eE/Fu/hXM115Jhx6wMyWg10IhnwYGJwExK8FfWlLNtvkGK8wfVjUcHC6N0rXMWUxTEKtor9+d48Q8IhW2QJYIjWsDlFjQc9EHurWeaT02eoYeCKPMXFovm80CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJp32suFwe3KUkxJQ6PL6eTTCqm0MB8GA1UdIwQYMBaAFJp32suFwe3KUkxJQ6PL6eTTCqm0MA0GCSqGSIb3DQEBCwUAA4IBAQBBvnn6f4pUKdzo3AqnrOSam315n4efdMwKPM3YsRogffeCJL1a9MDBaQ2FLTimjT/bJS7B9cfpBfoIG/ix/AQMRGoy8imUrFnoxCHhIWSWy80XAxQkGqErI59xzgqUTU3rNurq7WCazq9IyCQ3VJrg55v6UDfNkTag+nEEPbRSpHjJgZKdAKISihIdwkwSwvXFVTNDnScO++wgy4VuAJGL3ouAbQvx2iF/ge8uNqEP7Zz3GtN6o6Uy8Hfrxi7rla10ZxQ3HGJTQ0ixHA1DOQ9IHg3rQQzUR1dyCpfhfjsZ1T4aqhXbwLq5/FW8Tl38BU0d8V3Eu0WWeY+DbUdg5rUF</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfx566beda3-0c5f-f472-bdcb-2b09e9653270" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
<saml:Issuer>http://idp.service.com/metadata</saml:Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<ds:Reference URI="#pfx566beda3-0c5f-f472-bdcb-2b09e9653270">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>VzCbNSpQ/1iWYTsFTlWMbB9xx+ttOuIuEOpiBBOlc9I=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>HzmAJ2vPPjUHSXJczYb6Tnjx2iVwcmYc0TdyrSeoXYbMx17XEzzFRnSx03nZEsc+xbHmN++Mal7CwwvlFkSp9js3ubFRtLP70cJ7zNaQA83gfhHPu5roFRHqGoyKFvkJVIWnjnYrL/fvOUNC0osFNll85a6geoJGvTrNK9i+LEQtkATuO5/NqrtqPLqFzJc3veC74ChreMXd/1FBZO8w6nfo74yPDk9taJ/QFYRQikZd4PhMtx0PtArsVmpoYZWjJ5Drcdyq1ELshWRQrVXgsY/iGwo8CAhTumWB89SnuraUR23B4vD4wiapsoQzzun6hXiTXgRo4nDrPlUXvM/v3A==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIDVjCCAj6gAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MRQwEgYDVQQKDAttZXRhcHMgSW5jLjETMBEGA1UECwwKbWNsb3VkIElkcDEPMA0GA1UEAwwGbWNsb3VkMB4XDTIxMDUyMTA1MjQyMVoXDTI2MDUyMDA1MjQyMVowPDEUMBIGA1UECgwLbWV0YXBzIEluYy4xEzARBgNVBAsMCm1jbG91ZCBJZHAxDzANBgNVBAMMBm1jbG91ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOldNOBsT8bmI3CqSIy3E76+6o4jFBSjRlU4DXLKKLUvTdqAl82FBCZFlSKOn6zNmihKpRwn0loHED+2Trla5zLN++vgRm5WsMkQBUqv/uq20EB0LG4cosMYseDABUrAy6PF+RkNlLDDViw7/POFgQgtRomNm6Yn3nUEmh6QHlX25A83nvUwUzV5R76jVe6elRXviB/U7WkzZHJVeLUTaxFMK/u0xL7eE/Fu/hXM115Jhx6wMyWg10IhnwYGJwExK8FfWlLNtvkGK8wfVjUcHC6N0rXMWUxTEKtor9+d48Q8IhW2QJYIjWsDlFjQc9EHurWeaT02eoYeCKPMXFovm80CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJp32suFwe3KUkxJQ6PL6eTTCqm0MB8GA1UdIwQYMBaAFJp32suFwe3KUkxJQ6PL6eTTCqm0MA0GCSqGSIb3DQEBCwUAA4IBAQBBvnn6f4pUKdzo3AqnrOSam315n4efdMwKPM3YsRogffeCJL1a9MDBaQ2FLTimjT/bJS7B9cfpBfoIG/ix/AQMRGoy8imUrFnoxCHhIWSWy80XAxQkGqErI59xzgqUTU3rNurq7WCazq9IyCQ3VJrg55v6UDfNkTag+nEEPbRSpHjJgZKdAKISihIdwkwSwvXFVTNDnScO++wgy4VuAJGL3ouAbQvx2iF/ge8uNqEP7Zz3GtN6o6Uy8Hfrxi7rla10ZxQ3HGJTQ0ixHA1DOQ9IHg3rQQzUR1dyCpfhfjsZ1T4aqhXbwLq5/FW8Tl38BU0d8V3Eu0WWeY+DbUdg5rUF</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
<saml:Subject>
<saml:NameID SPNameQualifier="https://sp.saas.com/metadata" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="https://sp.saas.com/acs" InResponseTo="4fee3b046395c4e751011e97f8900b5273d56685" />
</saml:SubjectConfirmation>
</saml:Subject>
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.saas.com/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">role</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
</saml:Assertion>
</samlp:Response>
大まかに分けると Responseタグ、Issuerタグ、Singatureタグ、Statusタグ、Assertionタグがある構成になります。
Responseタグ
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="pfx64932347-5370-fea3-61bf-486ba519c861" Version="2.0" IssueInstant="2014-07-17T01:01:48Z" Destination="https://sp.saas.com/acs" InResponseTo="4fee3b046395c4e751011e97f8900b5273d56685">
タグ/属性 | 内容 | 備考 |
---|---|---|
ID | 一意のID | |
Version | SAMLのVersion | |
IssueInstant | SAML Responseが作成された時刻 | |
Destination | SP側でSAML Responseを受け取るURL | SAML RequestのAssertionConsumerServiceURLと同一の値が入る形になるはずです |
InResponseTo | SAML RequestのID | SP側が送ったSAML Requestに対するSAML Responseであるかを検証する際にこの値を参照します。IdP Initiatedの場合はSAML Requestを受け取るフローが無いのでここに値は入らない形になると思います。 |
Issuerダグ
<saml:Issuer>http://idp.service.com/metadata</saml:Issuer>
タグ/属性 | 内容 | 備考 |
---|---|---|
Issuer | URL形式のIdP側のIDみたいなもの? | 基本的にmetadataを返すURLが設定されます。 SAML RequestのIssuerはSP側のEntityIDが設定され、 SAML ResponseのIssuerにはIdP側のEntityIDが設定される形になります。 |
Signatureタグ
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1" />
<ds:Reference URI="#pfx64932347-5370-fea3-61bf-486ba519c861">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
<ds:DigestValue>VzCbNSpQ/1iWYTsFTlWMbB9xx+ttOuIuEOpiBBOlc9I=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>HzmAJ2vPPjUHSXJczYb6Tnjx2iVwcmYc0TdyrSeoXYbMx17XEzzFRnSx03nZEsc+xbHmN++Mal7CwwvlFkSp9js3ubFRtLP70cJ7zNaQA83gfhHPu5roFRHqGoyKFvkJVIWnjnYrL/fvOUNC0osFNll85a6geoJGvTrNK9i+LEQtkATuO5/NqrtqPLqFzJc3veC74ChreMXd/1FBZO8w6nfo74yPDk9taJ/QFYRQikZd4PhMtx0PtArsVmpoYZWjJ5Drcdyq1ELshWRQrVXgsY/iGwo8CAhTumWB89SnuraUR23B4vD4wiapsoQzzun6hXiTXgRo4nDrPlUXvM/v3A==</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIDVjCCAj6gAwIBAgIBATANBgkqhkiG9w0BAQsFADA8MRQwEgYDVQQKDAttZXRhcHMgSW5jLjETMBEGA1UECwwKbWNsb3VkIElkcDEPMA0GA1UEAwwGbWNsb3VkMB4XDTIxMDUyMTA1MjQyMVoXDTI2MDUyMDA1MjQyMVowPDEUMBIGA1UECgwLbWV0YXBzIEluYy4xEzARBgNVBAsMCm1jbG91ZCBJZHAxDzANBgNVBAMMBm1jbG91ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOldNOBsT8bmI3CqSIy3E76+6o4jFBSjRlU4DXLKKLUvTdqAl82FBCZFlSKOn6zNmihKpRwn0loHED+2Trla5zLN++vgRm5WsMkQBUqv/uq20EB0LG4cosMYseDABUrAy6PF+RkNlLDDViw7/POFgQgtRomNm6Yn3nUEmh6QHlX25A83nvUwUzV5R76jVe6elRXviB/U7WkzZHJVeLUTaxFMK/u0xL7eE/Fu/hXM115Jhx6wMyWg10IhnwYGJwExK8FfWlLNtvkGK8wfVjUcHC6N0rXMWUxTEKtor9+d48Q8IhW2QJYIjWsDlFjQc9EHurWeaT02eoYeCKPMXFovm80CAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJp32suFwe3KUkxJQ6PL6eTTCqm0MB8GA1UdIwQYMBaAFJp32suFwe3KUkxJQ6PL6eTTCqm0MA0GCSqGSIb3DQEBCwUAA4IBAQBBvnn6f4pUKdzo3AqnrOSam315n4efdMwKPM3YsRogffeCJL1a9MDBaQ2FLTimjT/bJS7B9cfpBfoIG/ix/AQMRGoy8imUrFnoxCHhIWSWy80XAxQkGqErI59xzgqUTU3rNurq7WCazq9IyCQ3VJrg55v6UDfNkTag+nEEPbRSpHjJgZKdAKISihIdwkwSwvXFVTNDnScO++wgy4VuAJGL3ouAbQvx2iF/ge8uNqEP7Zz3GtN6o6Uy8Hfrxi7rla10ZxQ3HGJTQ0ixHA1DOQ9IHg3rQQzUR1dyCpfhfjsZ1T4aqhXbwLq5/FW8Tl38BU0d8V3Eu0WWeY+DbUdg5rUF</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
こちらは署名の情報が入っています。
タグ/属性 | 内容 | 備考 |
---|---|---|
SignatureValue | 署名の値 | |
X509Certificate | 署名の検証に使用する証明書の値 | おそらくこのSAML Responseに記載されている証明書を読み取って使用する形は取らず、SP側に証明書の情報をアップロードさせ、その情報を元に検証する形が一般的な様です。 |
Statusタグ
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
</samlp:Status>
タグ/属性 | 内容 | 備考 |
---|---|---|
StatusCode | SAML Requestに対するStatusを返します。 | StatusCodeの種類に関しては「StatusCodeのCode一覧」にて記載します。 |
StatusCodeのCode一覧(前半のurn:oasis:names:tc:SAML:2.0:status
は省略して表示)
StatusCode | 内容 | 備考 |
---|---|---|
Success |
成功 | |
Requester |
SAML Reuestを出した側の問題で失敗 | 例えば SAML Requestに記載されているbindingの方法に対応していなかった場合など |
Responder |
SAML Responseを作る側の問題で失敗 | IdP側の設定不足の場合など? |
VersionMismatch |
SAML の Versionに対応していない | SAML Requestに記載されているVersionがIdP側で対応しているVersionではなかった場合 |
上記の内容は第一階層のStatusCodeになります。
StatusCodeは下記の様にStatusCodeの中にStatusCodeを置けます。
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Requester">
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported" />
</samlp:StatusCode>
この内側に記載するStatusCodeは結構たくさん種類があるため全ての解説は行いませんが種類としては下記の様な種類があります。
urn:oasis:names:tc:SAML:2.0:status:AuthnFailed
urn:oasis:names:tc:SAML:2.0:status:InvalidAttrNameOrValue
urn:oasis:names:tc:SAML:2.0:status:InvalidNameIDPolicy
urn:oasis:names:tc:SAML:2.0:status:NoAuthnContext
urn:oasis:names:tc:SAML:2.0:status:NoAvailableIDP
urn:oasis:names:tc:SAML:2.0:status:NoPassive
urn:oasis:names:tc:SAML:2.0:status:NoSupportedIDP
urn:oasis:names:tc:SAML:2.0:status:PartialLogout
urn:oasis:names:tc:SAML:2.0:status:ProxyCountExceeded
urn:oasis:names:tc:SAML:2.0:status:RequestDenied
urn:oasis:names:tc:SAML:2.0:status:RequestUnsupported
urn:oasis:names:tc:SAML:2.0:status:RequestVersionDeprecated
urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooHigh
urn:oasis:names:tc:SAML:2.0:status:RequestVersionTooLow
urn:oasis:names:tc:SAML:2.0:status:ResourceNotRecognized
urn:oasis:names:tc:SAML:2.0:status:TooManyResponses
urn:oasis:names:tc:SAML:2.0:status:UnknownAttrProfile
urn:oasis:names:tc:SAML:2.0:status:UnknownPrincipal
urn:oasis:names:tc:SAML:2.0:status:UnsupportedBinding
また、StatusMessage
タグやStatusDetail
タグを使用することでエラーメッセージ内容も指定することができます。
アサーションタグ
<saml:Assertion xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="pfx566beda3-0c5f-f472-bdcb-2b09e9653270" Version="2.0" IssueInstant="2014-07-17T01:01:48Z">
タグ/属性 | 内容 | 備考 |
---|---|---|
ID | 一意のID | |
Version | SAMLのVersion | |
IssueInstant | SAML Responseが生成されたタイミング |
Issuerタグ
<saml:Issuer>http://idp.service.com/metadata</saml:Issuer>
タグ/属性 | 内容 | 備考 |
---|---|---|
Issuer | IdPのEntityID |
Signatureタグ
アサーション内の署名になり、中身としてはResponseの署名と同様です。
Subjectタグ
<saml:Subject>
<saml:NameID SPNameQualifier="https://sp.saas.com/metadata" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient">_ce3d2948b4cf20146dee0a0b3dd6f69b6cf86f62d7</saml:NameID>
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
<saml:SubjectConfirmationData NotOnOrAfter="2024-01-18T06:21:48Z" Recipient="https://sp.saas.com/acs" InResponseTo="_4fee3b046395c4e751011e97f8900b5273d56685" />
</saml:SubjectConfirmation>
</saml:Subject>
タグ/属性 | 内容 | 備考 |
---|---|---|
NameID/SPNameQualifier | SPのEntityID | 信頼するSAMLリライングパーティ(SAML依拠当事者)を指定する? |
NameID/Format | NameIDの形式 | NameIDの形式の種類に関しては下記に記載 |
NameID | ログインを行うユーザーを識別するデータ(例: メールアドレス) | |
SubjectConfirmation/NotOnOrAfter | SAML Responseの検証時にこの時刻を過ぎている場合は検証を失敗させる | |
SubjectConfirmation/InResponseTo | SAML RequestのID | |
SubjectConfirmation/Recipient | Assertionの検証を行うエンドポイント |
Formatの形式の種類
Format | 内容 | 備考 |
---|---|---|
urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress |
メールアドレス | |
urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName |
ds:X509SubjectNameタグ内に記載されたものを参照? | |
urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName |
Windowsのdomain付きのユーザー名? | |
urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos |
Kerberos プリンシパル | |
urn:oasis:names:tc:SAML:2.0:nameid-format:entity |
Issuerの値を使用 |
Conditionsタグ
<saml:Conditions NotBefore="2014-07-17T01:01:18Z" NotOnOrAfter="2024-01-18T06:21:48Z">
<saml:AudienceRestriction>
<saml:Audience>https://sp.saas.com/metadata</saml:Audience>
</saml:AudienceRestriction>
</saml:Conditions>
タグ/属性 | 内容 | 備考 |
---|---|---|
NotBefore | 現在がこの時刻より前の時間の場合は無効とする | |
NotOnOrAfter | 現在がこの時刻以降の場合は無効とする | SubjectConfirmationにも同じ情報を持っているので、なぜ二つの箇所で持てるようにしているのかは不明 |
AudienceRestriction | Assertionを送る対象のSPの情報? | |
Audience | 対象のSPのEntityIDを指定 |
AuthnStatementタグ
<saml:AuthnStatement AuthnInstant="2014-07-17T01:01:48Z" SessionNotOnOrAfter="2024-07-17T09:01:48Z" SessionIndex="_be9967abd904ddcae3c0eb4189adbe3f71e327cf93">
<saml:AuthnContext>
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef>
</saml:AuthnContext>
</saml:AuthnStatement>
タグ/属性 | 内容 | 備考 |
---|---|---|
AuthnInstant | 認証が開始される時刻 | |
SessionIndex | IdP側で管理しているSessionのIndex | それぞれのSPに対して特定のIDをIdPが発行している様な形? |
SessionNotOnOrAfter | セッションが切れる時刻 | |
AuthnContext/AuthnContextClassRef | IdPにてユーザーを認証した方法 | AuthnContextClassRefの種類は下記に記載 |
AuthnContextClassRefの種類
urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol
urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocolPassword
urn:oasis:names:tc:SAML:2.0:ac:classes:Kerberos
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorUnregistered
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorUnregistered
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileOneFactorContract
urn:oasis:names:tc:SAML:2.0:ac:classes:MobileTwoFactorContract
urn:oasis:names:tc:SAML:2.0:ac:classes:Password
urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
urn:oasis:names:tc:SAML:2.0:ac:classes:PreviousSession
urn:oasis:names:tc:SAML:2.0:ac:classes:X509
urn:oasis:names:tc:SAML:2.0:ac:classes:PGP
urn:oasis:names:tc:SAML:2.0:ac:classes:SPKI
urn:oasis:names:tc:SAML:2.0:ac:classes:XMLDSig
urn:oasis:names:tc:SAML:2.0:ac:classes:Smartcard
urn:oasis:names:tc:SAML:2.0:ac:classes:SmartcardPKI
urn:oasis:names:tc:SAML:2.0:ac:classes:SoftwarePKI
urn:oasis:names:tc:SAML:2.0:ac:classes:Telephony
urn:oasis:names:tc:SAML:2.0:ac:classes:NomadTelephony
urn:oasis:names:tc:SAML:2.0:ac:classes:PersonalTelephony
urn:oasis:names:tc:SAML:2.0:ac:classes:AuthenticatedTelephony
urn:oasis:names:tc:SAML:2.0:ac:classes:SecureRemotePassword
urn:oasis:names:tc:SAML:2.0:ac:classes:TLSClient
urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken
urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified
かなり大量にあるのですが、ほとんどの場合ID/Passwordを使用してIdPにログインすると思うのでSAML Responseではurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
が選択されている形になると思います。
SP側でユーザーのIdPへの認証方法を指定したい場合はSAML Request
のAuthnContextClassRef
を指定することでIdP側で実装が行われていれば指定した方法でユーザーをログインさせる形になると思います。
AttributeStatementタグ
<saml:AttributeStatement>
<saml:Attribute Name="uid" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">test@example.com</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="eduPersonAffiliation" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
<saml:AttributeValue xsi:type="xs:string">users</saml:AttributeValue>
<saml:AttributeValue xsi:type="xs:string">role</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
タグ/属性 | 内容 | 備考 |
---|---|---|
Attribute/Name | Attribute名を指定 | これといって決まった記載の方法はなく、SPごとで値の指定の仕方は異なる様です。 |
Attribute/NameFormat | AttributeのNameの形式 | URI形式で指定する場合などNameの指定方法に関しては種類があるようで、その方法を指定する形になる様です。 |
Attribute/AttributeValue | Attributeの値が入ります。 | ユーザーのプロビジョニングで使用する場合はSPによってはSSO時にEmailの情報を必須入力させるなど使用用途は色々ありそうです。 |
Attribute/NameFormatは下記の三種類がある様です。
urn:oasis:names:tc:SAML:2.0:attrname-format:uri
urn:oasis:names:tc:SAML:2.0:attrname-format:basic
urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified
SAMLの実装で詰まるところ
同じ目的の値がいろんな箇所にあるため、なぜその値がそのタグに必要なのか不明なものがちょくちょくあります。
例えばNotOnOrAfter
はSubject
タグにもCondition
タグにもありますし、署名に関してはResponseに署名を行う場合もあればAssertionに署名を行う場合、両方に署名を行う場合など様々です。
そのため、まず仕様に関して何が正しいのか、どこを参照してどんな検証を行えば良いかわかりづらい気がしています。
また、仕様が複雑でAuthnContextClassRef
の内容だけで4000行近いドキュメントがあります。
この内容を全て理解するのは厳しく、かつ英語なためさらに理解が大変そうです。
仕様が複雑であることから、例えばAuthnContextClassRef
の値が送られてきても、この値をみて挙動を変える様なところまで実装できていないため無視していたり、InResponseToを検証するSaaSもあればしないSaaSもあったりみたいな感じになっていると思います。
こういった仕様の複雑さからくるIdP SP側の実装の差異が発生してどのような動きが正しいかわからない箇所があるため実装に困る場面があるのでは無いかなと思っています。
SSOだけでこんなに複雑なので、これに合わせてSLOやプロビジョニングも入ってくると実装と仕様の把握に結構な時間がかかってくるのでは無いかなと思いました。
SAML関係で参考になるサイトのリンク
SAML連携を行うためのOSSを作っています
メタップスでは現在SaaS一元管理ツールである「メタップスクラウド」の開発を行っています。
ただ、現状としてSaaSがSAMLに対応していない場合があり、SaaSの一元管理を行う際にSAMLを使ったSSOが実現できないSaaSが少なからずあります。
SaaS事業者がSAMLを導入しない理由としては実装コストが高かったり、優先順位が低いといった理由があり、そういった状況を踏まえ、SaaS事業者が簡単にSAML連携を導入できる様にするためのライブラリがあったら良い思い、現在はSAML連携を既存のアプリに導入するためのOSS開発を行っています。
内容としては、設定ファイルを用意することで細かい実装はしなくてもSSOが実装できる様なライブラリの開発を行っています。
興味ある方は是非見てみてください。