SAML在SSO中的應用


本篇文章來源於IBM:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1111_luol_sso/1111_luol_sso.html

SAML 2.0 簡介

SAML(Security Assertion Markup Language) 安全斷言標記語言是由標識化組織 OASIS 提出的用於安全互操作的標准。SAML 是一個 XML 框架,由一組協議組成,用來傳輸安全聲明。SAML 獲得了廣泛的行業認可,並被諸多主流廠商所支持。SAML 的初始版本 1.0 最初於 2002 年發布,發展數年后,於 2005 年推出 SAML 2.0。實際上,SAML 2.0 是由三個原有的認證聯邦標准:SAML1.1,ID-FF (Identity Federation Framework) 1.2 和 Shibboleth 構成。

圖 1. SAML 2.0 發展路線圖

圖 1. SAML 2.0 發展路線圖

SAML 2.0 是在以下幾個技術標准的基礎上構建的:

  • Extensible Markup Language (XML)
  • XML Schema
  • XML Signature
  • XML Encryption (SAML 2.0 only)
  • Hypertext Transfer Protocol (HTTP)
  • SOAP

SAML 2.0 的核心內容被涵蓋在官方文檔 SAMLConform, SAMLCore, SAMLBind 和 SAMLProf 中。SAML 2.0 規范說明書主要包含以下四方面內容:

  • SAML Assertions 斷言:定義交互的數據格式 (XML)
  • SAML Protocols 協議:定義交互的消息格式 (XML+processing rules)
  • SAML Bindings 綁定:定義如何與常見的通信協議綁定 (HTTP,SOAP)
  • SAML Profile 使用框架:給出對 SAML 斷言及協議如何使用的建議 (Protocols+Bindings)
圖 2. SAML 協議核心

圖 2. SAML 協議核心

在介紹 SAML 四大內容之前,簡單介紹下在 SAML 協議標准中出現的兩個角色,一個是 Identity Provider(IdP),通常 IdP 負責創建、維護和管理用戶認證。一個是 Service Provider(SP),通常 SP 控制用戶是否能夠使用該 SP 提供的服務和資源。

SAML 斷言定義了一系列 XML 編碼格式安全斷言的語法和語義規范,通常斷言由 SAML IdP 端生成並發送到 SAML SP 端,由 SP 端來分析和處理斷言。斷言內容中可能包含三類聲明(statements),聲明是 SP 端用來分析並判斷用戶能否接入服務或資源的依據:

  • 認證聲明:聲明用戶是否已經認證,通常用於單點登錄。
  • 屬性聲明:聲明某個 Subject 所具有的屬性。
  • 授權決策聲明:聲明某個資源的權限,即一個用戶在資源 R 上具有給定的 E 權限而能夠執行 A 操作。

SAML Protocols 描述了 SAML 元素(包括斷言)如何被打包到 SAML 請求和響應元素中,並規定 SAML 實體(IdP、SP 等)處理這些元素時必須遵守的處理規則。在大多數情況下,SAML Protocols 就是一個簡單的請求 - 響應協議。

SAML Bindings( 綁定 ) 是 SAML Protocols 信息到一個標准信息格式或者通信協議的映射過程。例如 SAML SOAP 綁定就定義了一個 SAML 消息如何被封裝到 SOAP envelope 中。

SAML Profile( 使用框架 ) 描述了如何使用 SAML 協議信息和斷言來處理特定的業務用戶實例。SAML 2.0 較之 SAML1.1 提供了更多使用框架,不過目前最常被使用的依然是 Web Browser SSO。本文下一章將重點介紹 Web Browser SSO Profile。


SAML 2.0 Web Browser SSO 使用框架

Web Browser SSO 定義了如何使用 SAML 消息和綁定來支持 web SSO。該使用框架提供了多種選項,不過首先需要做的兩個決定是:一,該消息流是由 IdP 發起還是由 SP 發起。二,在 Idp 和 SP 之間用哪類 SAML Binding 來傳遞消息。SAML 支持兩類消息流來實現 web SSO 信息交互,最常用的 web SSO 信息交互方式是由 SP 發起,用戶選擇一個瀏覽器標簽或者點擊一個鏈接,然后用戶將被轉到他們需要連入的 SP 應用。但是,這個時候用戶在 SP 端並沒有獲得任何認證許可,因此 SP 將用戶轉向 IdP 來獲得認證,Idp 構建一個 SAML Assertion 斷言來代表用戶在 IdP 側的認證,然后將帶有斷言的用戶實體轉向到 SP 端。由 SP 來處理斷言並決定是否授予該用戶接入資源的權限。另一種 web SSO 消息交互是由 IdP 發起的,這需要用戶先訪問 IdP 然后點擊一個鏈接到 SP。同樣 IdP 需要構建一個斷言發送到 SP 端,由 SP 端決定用戶的授權。這個方法在某些場景非常實用,但它需要 Idp 配置一個內部轉換鏈接到 SP 站點。本文根據生產環境實際應用場景,將主要介紹由 IdP 發起的 web SSO Profile。

SAML 2.0 HTTP POST BINDING

SAML 2.0 HTTP POST binding 定義了由 HTML 表單控制( Form Control)來傳送 SAML 協議消息的機制。這個綁定可能和 HTTP 重定向綁定(Redirect Binding)結合使用。HTTP Post Binding 是適用於當 SAML 請求者和響應者需要通過 HTTP 用戶代理來通信時。該綁定具體需要傳送的數據可以通過 SAML 2.0 的文檔描述文件中找到。需要注意的是該綁定要求傳送的 HTTP 表單數據使用 base64 編碼。在下一節將具體介紹 Web Browser SSO Profile 定義的如何使用 HTTP Post Binding 來傳送 SAML 消息。

SAML 2.0 Web Browser SSO 流程

上文中有提到 Web Browser SSO 可以由 IdP 端發起,也可以由 SP 端發起。本文將介紹由 IdP 端發起的 Web Browser SSO。

在由 IdP 發起的 SSO 用戶場景中,IdP 端配置了一個專門的鏈接來指向請求的 SP。這些鏈接實際上指向的是本地 IdP 的 SSO 服務,並傳遞參數到該 SSO 服務來鑒定遠程 SP。在該場景中,用戶並不是直接訪問 SP 端服務。而是連到 IdP 站點點擊其中的鏈接來獲得遠程 SP 服務的接入權限。這個鏈接觸發了 SAML 斷言的生成,在本案例中,將使用 HTTP POST Binding 來傳送信息到服務端:

圖 3. IdP 初始請求流程圖

圖 3. IdP 初始請求流程圖

  1. 如果用戶在 IdP 端沒有一個合法本地安全上下文的話,在某個時刻用戶將被要求提供他們的認證信息給 IdP 站點。
  2. 用戶提供有效的認證信息后,IdP 將為用戶創建一個本地登錄安全上下文。
  3. 用戶在 IdP 端選擇一個菜單選項或者鏈接以請求訪問一個 SP 的站點。比如 sp.example.com。這個動作將導致 IdP 端的 SSO 服務被調用。
  4. SSO 服務構建一個 SAML 斷言來表示用戶的登錄安全上下文,由於這里使用的是 POST binding。斷言將在被封裝到 SAML<Response> 之前進行數字簽名,然后 <Response> 消息將作為一個隱藏的表單控件被放入一個 HTML FORM 中,並且這個表單控件的名稱必須命名為“SAMLResponse”, 如果在 IdP 和 SP 端約定支持某一個特定的應用資源,那么可以將 SP 端的資源 URL 經過 Base64 編碼后,使用隱藏的表單控件加到表單中,該控件應被命名為“RelayState”。SSO 服務通過 HTTP Response 發送 HTML 表單給瀏覽器,通常出於使用方便的目的,該 HTML FORM 都是包含腳本代碼以自動執行提交表單的操作到目的站點。
     <form method="post"
     action="https://sp.example.com/SAML2/SSO/POST" ...> 
     <input type="hidden" name="SAMLResponse" value="response" /> 
     <input type="hidden" name="RelayState" value="token" />... 
     <input type="submit" value="Submit" /></form>

    其中 SAMLResponse 參數的值是基於 Base64 編碼。

  5. 瀏覽器根據用戶操作或者腳本的自動執行,產生一個 HTTP POST 請求來發送該表單給 SP 端的 Assertion Consumer Service(ACS)。
     POST /SAML2/SSO/POST HTTP/1.1 
     Host: sp.example.com 
     Content-Type: application/x-www-form-urlencoded 
     Content-Length: nnn 
     SAMLResponse=response&RelayState=token

    SP 端的 ACS 從 HTML FORM 中獲得 <Response> 消息來處理,ACS 必須首先驗證在 SAML 斷言中的數字簽名,然后再對斷言的內容進行處理以便在 SP 端給用戶創建一個本地的登錄安全上下文。一旦這個過程完成,SP 將取到 RelayState(如果有的話)來決定用戶期望訪問的應用資源 URL,並發送一個 HTTP 重定向響應給瀏覽器,將用戶定向到所請求的資源。

  6. 訪問檢查是為了確定用戶是否有正確的訪問權限來接入所請求的資源。如果訪問檢查通過,資源將被返回到瀏覽器。

Web SSO 用戶場景簡介

在上一章節,介紹了 SAML 2.0 中為實現 web SSO 所定義的 Web SSO Browser Profile。我們知道如果需要實現客戶端到服務端的 Web SSO,首先需要在 IdP 端實現 SSO 服務。而在 SAML 2.0 標准中,由於引入了聯邦認證的概念,在一個龐大的 IT 系統環境中,IdP 有可能作為一個獨立的第三方服務出現。但在本文中使用的 IdP,是由客戶一端自有 IT 認證系統提供的服務,即在服務提供商與客戶之間沒有引入第三方的 IdP,因此略去了 SP 與客戶需要在 IdP 注冊服務的過程。在下文中客戶端 SSO 服務將指代 IdP 的認證服務。

這里簡單介紹下文中使用的 Web SSO 用戶場景:終端用戶點擊企業內部網站某個鏈接或某個菜單選項時,客戶端 Web SSO 服務將被調用並發送包含一個 SAML Assertion 的 HTTP FORM 請求給服務端,該 SAML 斷言攜帶了斷言創建時間、用戶 ID 等信息,當服務端從表單中提取斷言信息后分析認為該斷言在有效期內,那么取得用戶 ID 信息后,在服務端用戶管理服務(例如 LDAP、DB)中查找該用戶 ID,如果找到匹配的用戶 ID,那么認為該服務請求有效,並將用戶重定向到請求的資源。該過程並不需要用戶再次輸入用戶名密碼等信息。但是在客戶端需要產生一個攜帶 SAML 斷言的表單請求。下面的內容就將介紹如何實現客戶端的請求以及服務端的 Assertion Consumer Service。


客戶端 Web SSO 的實現

在本小節,將詳細介紹如何使用 OpenSAML 庫來實現客戶端 SSO 服務。當客戶與 SP 協商好使用 HTTP POST Binding 來傳遞服務,並由客戶一端(IdP)來發起該 web SSO 之后,需要實現 web SSO 的客戶端部分,可以理解為當終端用戶在點擊企業內部網站某個鏈接或某個菜單選項時,能夠發送包含 SAML 斷言在內的 <SAMLResponse> 表單給服務端。包含 HTML FORM 表單的 html 文件的編寫是一個相對簡單的過程,讀者可以參考網路上的資源。

    <form method="post" action="https://sp.example.com/SAML2/SSO/POST" ...> 
    <input type="hidden" name="SAMLResponse" value="response" /> 
    <input type="hidden" name="RelayState" value="token" /> 
    ... 
    <input type="submit" value="Submit" /> 
    </form>

其中控件名為 SAMLResponse 的值如何生成是最關鍵的部分,是下面將要具體介紹的。上述表單代碼中名為 SAMLResponse 表單元素的值為“response”,未來我們將使用經過 base64 編碼后的 <Response> 消息的字符串來代替。注意,由於 SAML 2.0 標准的規定,攜帶 SAML 2.0 斷言的 <Response> 必須命名為”SAMLResponse”。盡管如此,並不意味着它是一個響應消息,在 SP 端看來,這是一個 SAML 2.0 SSO 的 HTTP 請求中攜帶一個字段信息。

SAML 2.0 SSO 請求的構成實現

根據 SAML 2.0 標准的要求,當一個 SAML Response 包含 0 個或多個斷言時,需要使用 <Response> 消息元素。這里給出 <Response> 的一個實例,我們將基於這個實例來討論它的實現過程。

 <samlp:Response ID="_0dac9fb0c5fedaae24e26d2eb4ffe8a4" 
 IssueInstant="2011-08-23T08:28:09.109Z" 
 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">http://mycom.com/issuer</saml:Issuer> 
 <samlp:Status> 
 <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success" /> 
 </samlp:Status> 
 <saml:Assertion ID="_9fa97d8e3552f2d4ae1fc001c887c614" 
 IssueInstant="2011-08-23T08:28:09.078Z" Version="2.0" 
 xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"> 
 <saml:Issuer>http://mycom.com/issuer</saml:Issuer> 
 <saml:Subject> 
 <saml:NameID Format="urn:oasis:names:tc:SAML:2.0: 
 nameid-format:emailAddress">****@cn.ibm.com</saml:NameID> 
 <saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> 
	 <saml:SubjectConfirmationData NotOnOrAfter="2011-08-23T08:58:06.578Z" 
	 Recipient="http://ip:port/SSO/serviceA" /> 
	  </saml:SubjectConfirmation> 
		 </saml:Subject> 
		 <saml:Conditions> 
		 <saml:AudienceRestriction> 
		 <saml:Audience> https://saml.example.com </saml:Audience> 
		 </saml:AudienceRestriction> 
  </saml:Conditions> 
 </saml:Assertion> 
 </samlp:Response>

通常,需要對 SAML 斷言進行數字簽名處理,這里為了使 Response 內容簡潔清晰,僅給出數字簽名前的 <Response> 消息,在后面的代碼實現中將提供數字簽名。在上面給出 Response 消息實例中,包含一個 Assertion,其中包含了用戶 ID 信息:<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress">****@cn.ibm.com</saml:NameID>,並標識該 NameID 格式為郵件地址。<NameID> 元素是封裝在 < Subject > 中的一個子元素。根據 SAML 2.0 標准規定,進行認證請求的斷言中,<Assertion> 元素必須包含一個具有 <Subject> 元素的 <AuthnStatement>, 並且該 <Subject> 元素必須包含一個 <SubjectConfirmation>。而對不同的目的的 Assertion,必須攜帶的信息字段不同,具體請參考 SAML 2.0 標准規定。下面是使用 OpenSAML 庫實現的 Subject 字段和 Assertion 字段的構建代碼:

 public Subject createSubject 
    (String username, String format, String confirmationMethod) 
 { 
    NameID nameID = create (NameID.class, NameID.DEFAULT_ELEMENT_NAME); 
        nameID.setValue (username); 
        if (format != null) 
    nameID.setFormat (format); 
  Subject subject = create (Subject.class, Subject.DEFAULT_ELEMENT_NAME); 
  subject.setNameID (nameID); 

        if (confirmationMethod != null) 
  { 
    SubjectConfirmation confirmation = create 
      (SubjectConfirmation.class,  SubjectConfirmation.DEFAULT_ELEMENT_NAME); 
    confirmation.setMethod (CM_PREFIX + confirmationMethod); 
    DateTime now=new DateTime(); 
    DateTime afterTime=now.plusMinutes(30); 
    SubjectConfirmationData subConData=create 
    (SubjectConfirmationData.class,SubjectConfirmationData.DEFAULT_ELEMENT_NAME); 
    subConData.setNotOnOrAfter(afterTime); 
    subConData.setRecipient(recipient); 
 confirmation.setSubjectConfirmationData(subConData); 
    subject.getSubjectConfirmations ().add (confirmation); 
  } 

  return subject;    
    }

完成 <Subject> 和 <Assertion> 的構建之后,需要對 <Assertion> 進行數字簽名之后再構建 <Response> 信息。由於數字簽名所涉及的內容不在本文討論范圍之內,因此不在此贅述。讀者可以參考本文所提供的代碼實現數字簽名部分。在完成 <Assertion> 的數字簽名之后,才能完成最終的 <Response> 信息的構建:

   /** 
根據給定的狀態碼,狀態消息和查詢 ID 來構建一個 Response 
 */ 
 public Response createResponse(String statusCode, String message, String inResponseTo) 
    { 
        Response response = create 
            (Response.class, Response.DEFAULT_ELEMENT_NAME); 
        response.setID (generator.generateIdentifier ()); 
        if (inResponseTo != null) 
            response.setInResponseTo (inResponseTo); 
        DateTime now = new DateTime (); 
        response.setIssueInstant (now); 
        if (issuerURL != null) 
        response.setIssuer (spawnIssuer ()); 
        StatusCode statusCodeElement = create 
            (StatusCode.class, StatusCode.DEFAULT_ELEMENT_NAME); 
        statusCodeElement.setValue (statusCode); 
        Status status = create (Status.class, Status.DEFAULT_ELEMENT_NAME); 
        status.setStatusCode (statusCodeElement); 
        response.setStatus (status); 

        if (message != null) 
        { 
            StatusMessage statusMessage = create 
                (StatusMessage.class, StatusMessage.DEFAULT_ELEMENT_NAME); 
            statusMessage.setMessage (message); 
            status.setStatusMessage (statusMessage); 
        } 
        
        return response; 
    }

根據 SAML 2.0 標准規定,我們提供給 SP 的 HTTP FORM 中,名為”SAMLResponse”表單控件的值需要經過 Base64 編碼后發送給 SP 端,因此需要對最終構建完成的 <Response> 進行 Base64 編碼,網上有很多類似的資源,讀者也可以參考本文提供的 Sample Code 來實現,這里將不做介紹。


服務端 Web SSO 的實現

當客戶端 Web SSO 服務發送生成的 HTTP 表單請求到服務端后,服務端的 Web SSO 服務需要處理來自客戶端的請求,解析 SAMLResponse 參數值並從中獲得用戶 ID 字段信息,當 SP 認為該用戶 ID 合法,則將用戶重定向到所請求的資源。本文服務端 Web SSO 將基於 WAS 的 Trust Association Interceptor 實現。


WAS TAI SSO 簡介

簡單來說,WAS Trust Association Interceptor(TAI) 信任聯合攔截器會攔截所有來自客戶端的請求,並對 http 請求內容進行分析,如果滿足 TAI 的認證要求,將認為用戶是合法的使用者,並將創建一個用戶信息對象傳遞給 WAS 繼續處理,根據應用配置的不同,通過 TAI 的認證后展現的內容不同。本章將簡單介紹 WAS TAI,重點介紹如何利用 OpenSAML 庫來解析來自客戶端的 SAMLResponse 請求。如果讀者對 TAI 的定制化實現感興趣,可以參考 IBM 提供的 Websphere Application Server 官方文檔。

WAS TAI 接口的兩個關鍵方法如下:

  • public boolean isTargetInterceptor(HttpServletRequest req) 如果 TAI 應該處理該請求,則該方法將返回 true。如果為 false 則告之 WAS TAI 忽略此次請求。
  • public TAIResult negotiateValidateandEstablishTrust(HttpServletRequest req, HttpServletResponse res) 該方法返回一個 TAIResult 對象,表明被處理的請求的狀態。如果有需要可以在此修改 HTTP 響應。

TAIResult 類有三個靜態方法來創建一個 TAIResult。三個靜態方法都將一個 int 類型值作為第一個參數。這個參數應該是一個合法的 HTTP 請求返回代碼。例如 HttpServletResponse.SC_OK 告訴 WAS TAI 完成協商,WAS 將使用在 TAIResult 中的信息來創建用戶身份。讀者可以在本文提供的實例代碼中看到如何實現自己定制化的 TAI。這里不再詳細介紹。在使用 isTargetInterceptor () 方法判斷來自客戶端的 SSO 屬於 SAML SSO 請求后,將使用 negotiateValidateandEstablishTrust() 方法來分析用戶的 SAML 2.0 SSO 請求,並根據認證的結果創建一個 TAIResult 對象。在該方法中調用了方法 TAISSOConsumer() 來解析 SAML 2.0Web SSO 請求。下面將介紹如何實現 TAISSOConsumer()( 即 SAML 2.0 服務端的 Assertion Consumer Service 部分 ).

SAML 2.0 SSO 請求的認證過程

TAISSOCoumsumer() 方法是實現 SAML 2.0 ACS 對 SAML Response 的認證處理。如果來自客戶端 SAML2 Response 經過加密與數字簽名處理,那么需要先解密 Assertion 然后對其進行數字簽名的驗證。上文的案例中對 Response 提供了數字簽名並經過 Base64 編碼,因此在本文中需要對 Response 先進行 Base64 解碼處理,然后進行認證數字簽名。下面是實現的代碼片段:

 public String parseResponse(String RspStr,JKSKeyData jksdata) { 
    String NameId = null; 
    //decode the response string 
    String SResponse=new String(Base64.decode(RspStr)); 
    //get the Credential information 
    char[] password =jksdata.getPassword(); 
        ……
    BasicX509Credential credential = new BasicX509Credential(); 
    credential.setEntityCertificate(certificate); 
    credential.setPublicKey(certificate.getPublicKey()); 
    ……
 // Initialize the library 
 DefaultBootstrap.bootstrap(); 
    // Get parser pool manager 
    BasicParserPool ppMgr = new BasicParserPool(); 
    ppMgr.setNamespaceAware(true); 
     // Parse metadata file 
    ...... 
     // Get apropriate unmarshaller 
 UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory(); 
 Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(metadataRoot); 
     // Unmarshall using the document root element 
    ……
    Response rsp=(Response)messageContext.getInboundMessage(); 
    // Verify issue time, make sure the assertion time is valid 
    DateTime time = rsp.getIssueInstant(); 
      ……
    //verify response signature 
 Signature rspSignature = rsp.getSignature(); 
……
 rspv.validate(rspSignature); 
    ……
 //verify assertion signature 
……
    //get the name identifier after verify successfully 
    NameId=samlAssertion.getSubject().getNameID().getValue(); 
    NameID nid=samlAssertion.getSubject().getNameID(); 
    return NameId; 
	 }

由 TAISSOCoumsumer() 返回的字符串 NameId 將被作為關鍵字,用來在 LDAP、DB 或者其他存儲用戶信息的地方查詢,如果該用戶能夠被查詢到,則整個 TAI 認證過程完成並返回成功的 HTTP 響應給用戶,如果沒有查詢到該用戶 ID,則認為 TAI 認證失敗,將返回一個失敗的 HTTP 響應或者錯誤頁面給用戶。


結束語

本文通過介紹 SAML 2.0 標准以及其中被普遍使用的 Web Browser SSO 使用框架,向讀者展示了如何依據 SAML 2.0 的使用框架並利用 OpenSAML 庫來實現客戶端與服務端的 Web SSO。本文中所涉及的 SAML 2.0 Web SSO 流程是一個被廣泛使用的框架,如果用戶希望實現更為復雜的使用框架,可以參考 SAML 2.0 的官方文檔與 OpenSAML 的官方站點。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM