天天看點

安全聲明标記語言SAML2.0初探

目錄

  • 簡介
  • SAML的構成
  • SAML的優勢
  • SAML是怎麼工作的
    • SP redirect request; IdP POST response
    • SP POST Request; IdP POST Response
    • SP redirect artifact; IdP redirect artifact
  • 總結

SAML的全稱是Security Assertion Markup Language, 是由OASIS制定的一套基于XML格式的開放标準,用在身份提供者(IdP)和服務提供者 (SP)之間交換身份驗證和授權資料。

SAML的一個非常重要的應用就是基于Web的單點登入(SSO)。

接下來我們一起來看看SAML是怎麼工作的。

在SAML協定中定義了三個角色,分别是principal:代表主體通常表示人類使用者。identity provider (IdP)身份提供者和service provider (SP)服務提供者。

IdP的作用就是進行身份認證,并且将使用者的認證資訊和授權資訊傳遞給服務提供者。

SP的作用就是進行使用者認證資訊的驗證,并且授權使用者通路指定的資源資訊。

為什麼要使用SAML呢?

第一可以提升使用者體驗,如果系統使用SAML,那麼可以在登入一次的情況下,通路多個不同的系統服務。這實際上也是SSO的優勢,使用者不需要分别記住多個系統的使用者名和密碼,隻用一個就夠了。

第二可以提升系統的安全性,使用SAML,我們隻需要向IdP提供使用者名密碼即可,

第三使用者的認證資訊不需要儲存在所有的資源伺服器上面,隻需要在在IdP中存儲一份就夠了。

接下來,我們通過一個用SAML進行SSO認證的流程圖,來分析一下SAML是怎麼工作的。

根據請求方式有redirect和post的不同,使用SAML來進行SSO認證有通常有三種方式,我們一一道來。

安全聲明标記語言SAML2.0初探

上圖中User Agent就是web浏覽器,我們看一下如果使用者想請求Service Provider的資源的時候,SAML協定是怎麼處理的。

  1. 使用者通過User Agent請求Service Provider,比如:
http://sp.flydean.com/myresource
           

SP将會對該資源進行相應的安全檢查,如果發現已經有一個有效的安全上下文的話,SP将會跳過2-7步,直接進入第8步。

  1. 如果在第一步的時候,SP并沒有找到相應的有效安全上下文的話,則會生成對應的SAMLRequest,并将User Agent重定向到IdP:
302 Redirect
Location: https://idp.flydean.com/SAML2/SSO/Redirect?SAMLRequest=request&RelayState=token
           

RelayState是SP維護的一個狀态資訊,主要用來防止CSRF攻擊。

其中這個SAMLRequest是用Base64編碼的samlp:AuthnRequest,下面是一個samlp:AuthnRequest的例子:

<samlp:AuthnRequest
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="aaf23196-1773-2113-474a-fe114412ab72"
    Version="2.0"
    IssueInstant="2020-09-05T09:21:59Z"
    AssertionConsumerServiceIndex="0"
    AttributeConsumingServiceIndex="0">
    <saml:Issuer>https://sp.flydean.com/SAML2</saml:Issuer>
    <samlp:NameIDPolicy
      AllowCreate="true"
      Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/>
  </samlp:AuthnRequest>
           

為了安全起見,SAMLRequest還可以使用SP提供的簽名key來進行簽名。

  1. User agent将會發送一個get請求到IdP的SSO server :
GET /SAML2/SSO/Redirect?SAMLRequest=request&RelayState=token HTTP/1.1
Host: idp.flydean.com
           

IdP收到這個AuthnRequest請求之後,将會進行安全驗證,如果是合法的AuthnRequest,那麼将會展示登入界面。

  1. 使用者可以輸入使用者名密碼進行登入。登入成功之後,IdP将會傳回一個XHTML form:
<form method="post" action="https://sp.flydean.com/SAML2/SSO/POST" ...>
    <input type="hidden" name="SAMLResponse" value="response" />
    <input type="hidden" name="RelayState" value="token" />
    ...
    <input type="submit" value="Submit" />
  </form>
           

這個form中包含了SAMLResponse資訊,SAMLResponse中包含了使用者相關的資訊。

同樣的SAMLResponse也是使用Base64進行編碼過的samlp:Response。

<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="2020-09-05T09:22:05Z"
    Destination="https://sp.flydean.com/SAML2/SSO/POST">
    <saml:Issuer>https://idp.flydean.com/SAML2</saml:Issuer>
    <samlp:Status>
      <samlp:StatusCode
        Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <saml:Assertion
      xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
      ID="identifier_3"
      Version="2.0"
      IssueInstant="2020-09-05T09:22:05Z">
      <saml:Issuer>https://idp.flydean.com/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"
            Recipient="https://sp.flydean.com/SAML2/SSO/POST"
            NotOnOrAfter="2020-09-05T09:27:05Z"/>
        </saml:SubjectConfirmation>
      </saml:Subject>
      <saml:Conditions
        NotBefore="2020-09-05T09:17:05Z"
        NotOnOrAfter="2020-09-05T09:27:05Z">
        <saml:AudienceRestriction>
          <saml:Audience>https://sp.flydean.com/SAML2</saml:Audience>
        </saml:AudienceRestriction>
      </saml:Conditions>
      <saml:AuthnStatement
        AuthnInstant="2020-09-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>
           

我們可以看到samlp:Response中包含有saml:Assertion資訊。

  1. user agent 收到XHTML form之後将會送出該form給SP。
  2. SP中的assertion consumer service将會處理這個請求,建立相關的安全上下文,并将user agent重定向到要通路的資源頁面。
  3. user agent再次請求SP資源。
  4. 因為安全上下文已經建立完畢,SP可以直接傳回相應的資源,不用再次到IdP進行認證。

我們可以看到上面的所有的資訊交換都是由前端浏覽器來完成的,在SP和IdP之間不存在直接的通信。

這種全部由前端來完成資訊交換的方式好處就是協定流非常簡單,所有的消息都是簡單的GET或者POST請求。

如果為了提高安全性,也可以使用引用消息。也就是說IdP傳回的不是直接的SAML assertion,而是一個SAML assertion的引用。SP收到這個引用之後,可以從背景再去查詢真實的SAML assertion,進而提高了安全性。

剛剛講的是SP redirect Request,這裡我們看一下SP POST request是怎麼做的:

安全聲明标記語言SAML2.0初探

和第一種方式的不同之處在于第二步和第三步。

第二步:SP不再進行redirect了,而是傳回一個XHTML form給User agent:

<form method="post" action="https://idp.flydean.com/SAML2/SSO/POST" ...>
    <input type="hidden" name="SAMLRequest" value="request" />
    <input type="hidden" name="RelayState" value="token" />
    ...
    <input type="submit" value="Submit" />
  </form>
           

第三步:拿到第二步的XHTML form之後,User agent将該form post到IdP SSO server。

從第四步開始就和第一種方式是一樣的了。

第三種方式,SP和IdP都用的是redirect,但是redirect的内容都是artifact。

之前我們講了SAML message可以以值的方式也可以以引用的方式來進行傳遞。

而這種以引用的傳遞方式就是artifact。

收到artifact的receiver會發送一個samlp:ArtifactResolve 給issuer,進而獲得真正的message。

下面是一個向IdP請求message的例子:

<samlp:ArtifactResolve
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="_cce4ee769ed970b501d680f697989d14"
    Version="2.0"
    IssueInstant="2020-09-05T09:21:58Z">
    <saml:Issuer>https://idp.flydean.com/SAML2</saml:Issuer>
    <!-- an ArtifactResolve message SHOULD be signed -->
    <ds:Signature
      xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
    <samlp:Artifact>AAQAAMh48/1oXIM+sDo7Dh2qMp1HM4IF5DaRNmDj6RdUmllwn9jJHyEgIi8=</samlp:Artifact>
  </samlp:ArtifactResolve>
           

相應的server會傳回一個包含samlp:AuthnRequest的samlp:ArtifactResponse:

<samlp:ArtifactResponse
    xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
    ID="_d84a49e5958803dedcff4c984c2b0d95"
    InResponseTo="_cce4ee769ed970b501d680f697989d14"
    Version="2.0"
    IssueInstant="2020-09-05T09:21:59Z">
    <!-- an ArtifactResponse message SHOULD be signed -->
    <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>
    <samlp:AuthnRequest
      xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
      xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
      ID="_306f8ec5b618f361c70b6ffb1480eade"
      Version="2.0"
      IssueInstant="2020-09-05T09:21:59Z"
      Destination="https://idp.flydean.com/SAML2/SSO/Artifact"
      ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact"
      AssertionConsumerServiceURL="https://sp.flydean.com/SAML2/SSO/Artifact">
      <saml:Issuer>https://sp.flydean.com/SAML2</saml:Issuer>
      <samlp:NameIDPolicy
        AllowCreate="false"
        Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
    </samlp:AuthnRequest>
  </samlp:ArtifactResponse>
           

看下第三種方式的流程圖:

安全聲明标記語言SAML2.0初探

可以看到這種方式和前面兩種方式的差別就是多了一個請求真實message的步驟。

以第三,四,五步為例:

第三步user agent請求IdP的SSO server:

https://idp.example.org/SAML2/SSO/Artifact?SAMLart=artifact_1&RelayState=token
           

注意這裡請求的參數變成了SAMLart。

第四步,IdP需要發送一個samlp:ArtifactResolve到SP來請求真正的samlp:AuthnRequest。

第五步,SP傳回一個samlp:ArtifactResponse 包含samlp:AuthnRequest。

SAML協定和它的基本用法就是上面這樣。下面的文章我們會舉一個具體的例子,來講解如何應用SAML協定。

本文作者:flydean程式那些事

本文連結:http://www.flydean.com/saml-startup/

本文來源:flydean的部落格

歡迎關注我的公衆号:「程式那些事」最通俗的解讀,最深刻的幹貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!

繼續閱讀