本記事について
Web サービス提供者が SAML 認証を利用して SSO を実現することにフォーカスし、そのために最低限必要な内容についてまとめることを目的とします。
SAML とは
SAML とは、Security Assertion Markup Language の略称で、インターネットドメイン間でユーザー認証を行なうためのXML(マークアップ言語)をベースにした標準規格です。現在の SAML 2.0 は、SAML 1.1 に代わり、2005 年 3 月に OASIS 標準として承認されました。
一度のログインで複数のサービスにログインできるシングルサインオン(SSO)を実現するための規格として利用されることが多いです。
SAML 認証を用いたログインの際、ユーザー認証だけでなく属性情報も認証できるため、ユーザーのアクセス範囲も制限できるという特徴を持ちます。
SAML の各仕様
SAML 2.0 に関する仕様は、下記の公式ドキュメント SAMLCore、SAMLBindings、SAMLProfiles、SAMLMetadata に詳細に記載されています。
つまり、これらを読めば SAML のことが分かるということです。ただ量が多いので、本記事では冒頭で述べたように、Web サービス提供者が SAML 認証を利用して SSO を実現することにフォーカスし、そのために最低限必要な内容についてまとめることを目的とします。
SAML 認証の概要
SAML 認証ではユーザー、SP(Service Provider)、IdP (Identity Provider)の三者間で認証情報をやり取りします。
- ユーザー
- クラウドサービスへログインする利用者
- SP(Service Provider)
- ログイン先となる Web サービス
- IdP(Identity Provider)
- Identify Provider の略で認証情報を提供するシステム
SAML 認証の手続きと処理
SAML 認証する際に必要な手続きと処理は下記となります。事前に 1 と 2 の作業をしておき、その上で 3 ~ 6 の処理を実装すれば SAML 認証による SSO が可能になります。また、下記のフローは SP 側を実装する想定で記載しています。
- SP が信頼する IdP の登録
- IdP が信頼する SP の登録
- 認証要求メッセージの作成
- 認証要求メッセージを IdP へ送信
- IdP が発行した認証応答メッセージの受信
- 認証応答メッセージの検証
それでは一つずつみていきましょう。
1. SP が信頼する IdP の登録
SP へ連携したい IdP の情報を事前に登録します。IdP の情報は Metadata を直接アップロードするか管理画面を通して登録します。
SP に登録する主な IdP 項目
主な項目 | 説明 |
---|---|
X509Certificate | IdP が認証応答メッセージの署名に用いる秘密鍵に対応する公開鍵 |
SingleSignOnService | IdP が認証要求メッセージを受け取るURL |
SP に登録する IDP Metadata 例
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://idp-domain" validUntil="2025-01-01T00:00:00.000Z"> <md:IDPSSODescriptor WantAuthnRequestsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:KeyDescriptor use="signing"> <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> <ds:X509Data> <ds:X509Certificate>[Certificate]</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </md:KeyDescriptor> <md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat> <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp-domain"/> <md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp-domain"/> </md:IDPSSODescriptor> </md:EntityDescriptor>
2. IdP が信頼する SP の登録
今度は逆に IdP へ連携したい SP の情報を事前に登録します。SP の情報は Metadata を直接アップロードするか管理画面を通して登録します。
IdP に登録する主な SP 項目
主な項目 | 説明 |
---|---|
entityID | IdP の識別子 |
X509Certificate | IdP が認証応答メッセージの署名に用いる秘密鍵に対応する公開鍵 |
SingleSignOnService | IdP が認証要求メッセージを受け取るURL |
idP に登録する Metadata 例
<md:EntityDescriptor entityID="https://sp-domain"> <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> <md:NameIDFormat> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress </md:NameIDFormat> <md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://sp-domain/acs" index="0"/> </md:SPSSODescriptor> </md:EntityDescriptor>
3. 認証要求メッセージの作成
認証要求メッセージは SAMLCore に定義されている<AuthnRequest>
要素で表現されます。
認証要求メッセージの主な項目
主な項目 | 説明 |
---|---|
AssertionConsumerServiceURL | SP が認証応答メッセージを受け取る URL |
ProtocolBinding | 認証応答メッセージを受け取る際に利用する SAMLBinding |
Issuer | SP 識別子。事前に登録した値(entityID)と違うとリクエストが通らない |
NameIDPolicy | 認証応答メッセージ内のユーザーの識別子に関するポリシー |
認証要求メッセージ例
<samlp:AuthnRequest xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="uq-id" Version="2.0" IssueInstant="2022-01-01T00:00:00Z" Destination="https://idp-domain" AssertionConsumerServiceURL="https://domain.com/saml/acs" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"> <saml:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">https://example.com</saml:Issuer> <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress" AllowCreate="true"/> </samlp:AuthnRequest>
4. 認証要求メッセージを IdP へ送信
<AuthnRequet>
を IdP に届けるための SAMLBinding を HTTP Redirect Binding を利用する場合、<AuthnRequet>
を以下の順序でエンコードします。
- Deflate エンコード
- Base64 エンコード
- URL エンコード
エンコード結果の文字列をクエリパラメータとして、IdP の SSO URL に付加し、その URL にリダイレクトすることで IdP に認証要求メッセージ送ります。その際のパラメータ名には SAMLRequst を用います。
例えば下記のようなコマンドに、認証要求メッセージとしてリダイレクトした URL の SAMLRequest パラメータの値を指定するとエンコード前の値が確認できます。
$ echo 'SAMLRequestの値' | php -R 'echo zlib_decode(base64_decode(urldecode($argn)));'
5. IdP が発行した認証応答メッセージを受信
IdP がユーザーの認証に成功すると、認証応答メッセージを SP に返します。認証応答メッセージは<Response>
要素で表現されます。<Response>
の内容を受け取る際の SAMLBindings は、SPメタデータや<AuthnRequest>
の ProtocolBinding 属性で指定が可能ですが、ここでは HTTP POST Binding を利用する例で記載します。詳しくは SAMLCore 3.4 の Authentication Request Protocol に記載されています。HTTP POST Binding は<Response>
が Base64 にエンコードされます。最終的に IdP は AssertionConsumerServiceURL に SAMLResponse という key 名で値を POST します。
SAMLResponse=<EncodedResponse>
例えば下記のようなコマンドに、認証応答メッセージとして POST された SAMLResponse の値を指定するとエンコード前の値が確認できます。
echo 'SAMLResponse' | php -R 'echo base64_decode($argn);'
6. 認証応答メッセージの検証
SP の AssertionConsumerService が受け取った <Response>
の内容を検証し、ログインの成否を判定します。レスポンス内容については、SAMLProfilesの 4.1.4.2 Regardless of the SAML binding used, the service provider MUST do the following:
として SAMLProfiles 4.1.4.3 <Response> Message Processing Rules
に記載されているので、詳細については公式ドキュメントを参考にしてください。
認証応答メッセージの主な項目
親要素 | 主な項目 | 説明 |
---|---|---|
Status | StatusCode | 認証結果ステータス |
Assertion | Issuer | IdP の識別子 |
Assertion | Signature | 証明書に関する情報 |
Assertion | Subject | ユーザーの識別子に関する情報 |
Assertion | Conditions | SP の AssertionConsumerServiceURL に関する情報 |
Assertion | AttributeStatement | 属性連携に関する情報 |
認証応答メッセージ例
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <saml2p:Response xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" Destination="https://sp-domain" ID="_5d" InResponseTo="id-c9" IssueInstant="2022-01-01T00:00:00.000Z" Version="2.0"> <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://idp-domain</saml2:Issuer> <saml2p:Status> <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> </saml2p:Status> <saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_da" IssueInstant="2022-01-01T00:00:00.000Z" Version="2.0"> <saml2:Issuer>https://idp-domain</saml2: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/2001/04/xmldsig-more#rsa-sha256" /> <ds:Reference URI="#_da"> <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/2001/04/xmlenc#sha256" /> <ds:DigestValue>ABC=</ds:DigestValue> </ds:Reference> </ds:SignedInfo> <ds:SignatureValue>[SignatureValue]</ds:SignatureValue> <ds:KeyInfo> <ds:X509Data> <ds:X509SubjectName>[SubjectName]</ds:X509SubjectName> <ds:X509Certificate>[Certificate]</ds:X509Certificate> </ds:X509Data> </ds:KeyInfo> </ds:Signature> <saml2:Subject> <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">username@techtouch.jp</saml2:NameID> <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <saml2:SubjectConfirmationData InResponseTo="id-123" NotOnOrAfter="2022-01-01T00:00:00.000Z" Recipient="https://sp-domain" /> </saml2:SubjectConfirmation> </saml2:Subject> <saml2:Conditions NotBefore="2022-01-01T00:00:00.000Z" NotOnOrAfter="2022-01-01T00:00:00.000Z"> <saml2:AudienceRestriction> <saml2:Audience>https://sp-domain/acl</saml2:Audience> </saml2:AudienceRestriction> </saml2:Conditions> <saml2:AttributeStatement> <saml2:Attribute Name="email"> <saml2:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:anyType">username@techtouch.jp</saml2:AttributeValue> </saml2:Attribute> </saml2:AttributeStatement> <saml2:AuthnStatement AuthnInstant="2022-01-01T00:00:00.000Z" SessionIndex="_da40fbbf79154bfe10d28c4d25f874d6"> <saml2:AuthnContext> <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml2:AuthnContextClassRef> </saml2:AuthnContext> </saml2:AuthnStatement> </saml2:Assertion> </saml2p:Response>
まとめ
Web サービス提供者が SAML 認証を利用して SSO を実現することにフォーカスし、そのために最低限必要な内容についてまとめてみました。また検証には、IdP として動作する OSS を利用したり、こちらのサイト を利用しました。ぜひ試してみてください。