SAML在SSO中的應用

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

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 構成。web

圖 1. SAML 2.0 發展路線圖

圖 1. SAML 2.0 發展路線圖

SAML 2.0 是在如下幾個技術標準的基礎上構建的:bootstrap

  • 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 就是一個簡單的請求 - 響應協議。app

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

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


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 的官方站點。

相關文章
相關標籤/搜索