🙄

身近なSAML認証の中身を見てみた

2023/10/14に公開

SAML認証とは

SAMLは、Security Assertion Markup Languageと呼ばれるSSOを実現するXML形式の標準規格です。OASISにより2002年に策定され、2005年にはバージョン2が出た比較的歴史のある規格です。

そのためセキュリティ的に信頼が置かれ、今でもエンタープライズなどではこのSAML認証がよく使われています。
今回はそのSAML認証の仕組みについて紹介します。

SAML認証のフロー


SAMLの認証フローは図の通りです。登場人物としては、

  • あるサービスプロバイダーにログインしたいユーザー
  • サービスプロバイダー(SP)。大学の学習管理システムだったり、DropboxやCiniiだったり、とにかく利用しようとしているサービス。
  • ユーザーの認証を担当しているアイデンティティプロバイダー(IdP)。Azure AD(Active Directory)など。

3人です。

ユーザーはSPにログイン要求をすると、受け取ったSPは、

とユーザーをリダイレクトでIdPに飛ばし、IdPに対するSAML認証要求を出します。それを受け取ったIdPは、

とユーザーに対してログイン画面を表示し、ユーザーID・パスワード・場合によっては2段階認証などを用いてユーザーを認証します。

ユーザーが認証を通過した場合、IdPは先ほどのSPに対してSAMLアサーションを送ります。これは要は、

というような情報です。このSAMLアサーションは署名付き(IdPの秘密鍵での署名)で、受け取ったSPはその署名が適切か(本当に依頼したIdPから来たものなのか)を検証します。

この検証が通ったら、SPはSAMLアサーションで受け取ったユーザー情報をもとに、そのユーザーに対応するアカウントにアクセス許可を渡し、これによってユーザーはサービスにログインすることができます。
以上がSAML認証の流れです。

SAML認証の一例

では、SAML認証の例を1つ見てみましょう。

使用したツールは、Google Chromeの拡張機能であるSAML Tracerです。
SAML Tracerを起動して大学の自分のページにアクセスを試みました。

すると、SAMLが2箇所で使われていることがわかります。
これはSAMLリクエストと、そのレスポンスであるSAMLアサーションです。

では、実際に中身を見てみましょう。

SAMLリクエストの中身

SAMLリクエストの中身は以下のような形でした。

SAMLリクエスト
<samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                    AssertionConsumerServiceURL="https://utas.adm.u-tokyo.ac.jp/Shibboleth.sso/SAML2/POST"
                    Destination="https://login.microsoftonline.com/AzureAD上の大学テナントID/saml2"
                    ID="_b1111111111111111111111111111111"
                    IssueInstant="2023-10-13T08:15:17Z"
                    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                    Version="2.0"
                    >
    <saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://utas.adm.u-tokyo.ac.jp/shibboleth-sp</saml:Issuer>
    <samlp:NameIDPolicy AllowCreate="1" />
</samlp:AuthnRequest>

このように、SAMLは全てXML形式で書かれているため、<AuthRequest></AuthRequest>のようにタグ付きで表記されます。またここでは<AuthRequest>ということからSAMLの認証要求
(Authentication Request)であることが読み取れますね。

では、この中身についてです。

最初のsamlpはSAMLのプロトコルを表しており、SAML2.0が使われていることがわかります(2005年にリリースされたやつですね。SAMLは2.0が現状一番新しいです)。

次のAssertionConsumerServiceURLはSAMLアサーションを返して欲しい場所、すなわちSPのエンドポイントURLです。これは次のSAMLアサーションのDestinationで書かれているところと一致していることを確認してください。またここでShibboleth(シボレス)という単語に注目してください。これは主に教育・研究機関での利用を目的として開発されているSSOを実現するオープンソースのソフトウェア(OSS)のことです。これがSAMLベースの認証を活用しているため、SAMLが使われているわけです。詳しくは別の記事に書きます。

Destinationはリクエストの到着地点、要はIdPです。まあIdPに飛ばしていますもんね。ここでURLがhttps://login.microsoftonline.com/に送られていることがわかりますね。これで裏ではMicrosoftのAzureAD(Active Directory)を使ったユーザー管理が行われていることがわかりますね。

次のIDはリクエストのIDです。ユーザーのIDとは関係ありません。この時点ではSPはログイン要求してきたユーザーがどのユーザーかなどは一切わかってなく、とりあえずログインしたいやつが来たから身元を確認してくれってIdPに認証を頼んでいるだけです。

そして次に要求を出した時刻が書かれており、次のバインディングは返しのプロトコルを指定しています。ここではHTTPのPOSTで返してねということですね。そしてIssuerに関してはこのSAML要求を発行したもので、SPのURLが載せられています。

以上がSAMLリクエストです。
要は、

というような要求です。

SAMLアサーションの中身

SAMLアサーションの中身は以下のようになりました。

SAMLアサーション
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
                ID="_e000000000000000000000000000000"
                Version="2.0"
                IssueInstant="2023-10-13T08:15:17.327Z"
                Destination="https://utas.adm.u-tokyo.ac.jp/Shibboleth.sso/SAML2/POST"
                InResponseTo="_b1111111111111111111111111111111"
                >
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://sts.windows.net/AzureAD上の大学テナントID/</Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" />
    </samlp:Status>
    <Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
               ID="_c12312312312312312312312312312312312"
               IssueInstant="2023-10-13T08:15:17.323Z"
               Version="2.0"
               >
        <Issuer>https://sts.windows.net/AzureAD上の大学テナントID/</Issuer>
        <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
            <SignedInfo>
                <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
                <Reference URI="#_c12312312312312312312312312312312312">
                    <Transforms>
                        <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                        <Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
                    </Transforms>
                    <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                    <DigestValue>123123123123123123123123123123123123=のような値</DigestValue>
                </Reference>
            </SignedInfo>
            <SignatureValue>11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111==みたいな感じの値</SignatureValue>
            <KeyInfo>
                <X509Data>
                    <X509Certificate>111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111みたいな感じの値</X509Certificate>
                </X509Data>
            </KeyInfo>
        </Signature>
        <Subject>
            <NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">大学での自分のID@大学のメール</NameID>
            <SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
                <SubjectConfirmationData InResponseTo="_b1111111111111111111111111111111"
                                         NotOnOrAfter="2023-10-13T09:15:17.244Z"
                                         Recipient="https://utas.adm.u-tokyo.ac.jp/Shibboleth.sso/SAML2/POST"
                                         />
            </SubjectConfirmation>
        </Subject>
        <Conditions NotBefore="2023-10-13T08:10:17.244Z"
                    NotOnOrAfter="2023-10-13T09:15:17.244Z"
                    >
            <AudienceRestriction>
                <Audience>https://utas.adm.u-tokyo.ac.jp/shibboleth-sp</Audience>
            </AudienceRestriction>
        </Conditions>
        <AttributeStatement>
            <Attribute Name="http://schemas.microsoft.com/identity/claims/tenantid">
                <AttributeValue>AzureAD上の大学テナントID</AttributeValue>
            </Attribute>
            <Attribute Name="http://schemas.microsoft.com/identity/claims/objectidentifier">
                <AttributeValue>また別のID</AttributeValue>
            </Attribute>
            <Attribute Name="http://schemas.microsoft.com/identity/claims/identityprovider">
                <AttributeValue>https://sts.windows.net/AzureAD上の大学テナントID/</AttributeValue>
            </Attribute>
            <Attribute Name="http://schemas.microsoft.com/claims/authnmethodsreferences">
                <AttributeValue>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AttributeValue>
                <AttributeValue>http://schemas.microsoft.com/claims/multipleauthn</AttributeValue>
            </Attribute>
            <Attribute Name="urn:oid:0.9.2342.19200300.100.1.1">
                <AttributeValue>大学での自分のID</AttributeValue>
            </Attribute>
        </AttributeStatement>
        <AuthnStatement AuthnInstant="2023-05-11T03:14:23.379Z"
                        SessionIndex="_c12312312312312312312312312312312312"
                        >
            <AuthnContext>
                <AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</AuthnContextClassRef>
            </AuthnContext>
        </AuthnStatement>
    </Assertion>
</samlp:Response>

こんな感じです。とりあえずめっちゃ長いですよね。SAMLはXMLベースで、XMLはタグベース(<タグ></タグ閉じ>)なので、いちいち分量が多くなってしまい、見た目も複雑になってしまいます。ここで通信なのでデータ量が大きくなると負担ですし、読む側としてもデバッグが大変になります。なので最近はXMLではなくJSONベースのJWT(JSON Web Token)を使ったOpenID Connectなどがよく使われるようになっています。

ということで中身をもっとしっかりみていきましょう。

まずDestinationが先ほどのAssertionConsumerServiceURLと一致していることがわかります。同様に、InResponseToが先ほどのIDと一致していることがわかります。要は「あのリクエストの返しだよ!」ってことですね。

次に

<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">https://sts.windows.net/AzureAD上の大学テナントID/</Issuer>

の部分ですけど、あれ、IdPが先ほどのリクエストでのDestinationであるlogin.microsoft.comじゃない??ってなると思います。ですがこれもAzureADのことを表しており、このドメインであるsts.windows.netはAzure ADのセキュリティトークンサービス (STS) のエンドポイントです。まあこれは、SAMLリクエストを受け付けたのはlogin.microsoft.comでのエンドポイントだけど、SAMLアサーションを発行したのはAzureADのセキュリティトークンサービスであるsts.windows.netだったよってことです。
そしてstatus:Successと書かれているので、認証が通ったということですね。

そしてその下のAssertionという部分が、実際のSAMLアサーションになります。ここで、<Signature>という属性があることに注目してください。
ここが署名部分です。

ここで、一番初めのフローの図を見て欲しいです。
この署名の中の、<SignedInfo>という属性の中身に書かれているものが、署名の計算に使用される情報を格納されています。例えば

<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />

が正規化方式(Canonicalization Method)です。実際に書かれているurlにアクセスすると、その正規化の仕組みが書かれています。
また、

<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />

はハッシュ関数の種類を指していて、sha256が採用されていることがわかります。

そして、
<SignatureValue>: 計算されたデジタル署名の実際の値。
<KeyInfo>: 公開鍵に関する情報。
<X509Data>: X.509証明書のデータ。
<X509Certificate>: 実際のX.509証明書のBASE64エンコードされた値。これは署名の検証に使用される公開鍵を含んでいます。
ここに公開鍵の情報も提供されていますが、SPは直接この公開鍵を用いてこのSAMLアサーションが本物かの検証を行うわけではありません(だとしたら嘘のIdPが自分の秘密鍵で署名し自分の公開鍵を渡すことができてしまう)。通常、SAMLでは事前のセットアップとしてこの公開鍵をはじめとした情報をメタデータとしてIdP側で発行し、それをSP側で取り込む設定をする必要があります。

次にSubjectに関してです。これがSP側でユーザーの識別子となるものです。また、AttributeがそのユーザーがAzureAD上で属しているグループなどのオブジェクトIDが返されています(工学部・大学4年、のような感じのグループ)。

以上が実際のSAML認証の中身でした。

最後

今回はSAML認証の仕組みと中身について見てみました。
こんな形でSSOが実現されているのですね。

Discussion