本篇文章來源於IBM:http://www.ibm.com/developerworks/cn/websphere/library/techarticles/1111_luol_sso/1111_luol_sso.htmlhtml
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
SAML 2.0 是在如下幾個技術標準的基礎上構建的:bootstrap
SAML 2.0 的核心內容被涵蓋在官方文檔 SAMLConform, SAMLCore, SAMLBind 和 SAMLProf 中。SAML 2.0 規範說明書主要包含如下四方面內容:瀏覽器
在介紹 SAML 四大內容以前,簡單介紹下在 SAML 協議標準中出現的兩個角色,一個是 Identity Provider(IdP),一般 IdP 負責建立、維護和管理用戶認證。一個是 Service Provider(SP),一般 SP 控制用戶是否可以使用該 SP 提供的服務和資源。安全
SAML 斷言定義了一系列 XML 編碼格式安全斷言的語法和語義規範,一般斷言由 SAML IdP 端生成併發送到 SAML SP 端,由 SP 端來分析和處理斷言。斷言內容中可能包含三類聲明(statements),聲明是 SP 端用來分析並判斷用戶可否接入服務或資源的依據:併發
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
回頁首post
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 定義了由 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 消息。
上文中有提到 Web Browser SSO 能夠由 IdP 端發起,也能夠由 SP 端發起。本文將介紹由 IdP 端發起的 Web Browser SSO。
在由 IdP 發起的 SSO 用戶場景中,IdP 端配置了一個專門的連接來指向請求的 SP。這些連接實際上指向的是本地 IdP 的 SSO 服務,並傳遞參數到該 SSO 服務來鑑定遠程 SP。在該場景中,用戶並非直接訪問 SP 端服務。而是連到 IdP 站點點擊其中的連接來得到遠程 SP 服務的接入權限。這個連接觸發了 SAML 斷言的生成,在本案例中,將使用 HTTP POST Binding 來傳送信息到服務端:
<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 編碼。
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 重定向響應給瀏覽器,將用戶定向到所請求的資源。
在上一章節,介紹了 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。
在本小節,將詳細介紹如何使用 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 標準的要求,當一個 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 服務發送生成的 HTTP 表單請求到服務端後,服務端的 Web SSO 服務須要處理來自客戶端的請求,解析 SAMLResponse 參數值並從中得到用戶 ID 字段信息,當 SP 認爲該用戶 ID 合法,則將用戶重定向到所請求的資源。本文服務端 Web SSO 將基於 WAS 的 Trust Association Interceptor 實現。
簡單來講,WAS Trust Association Interceptor(TAI) 信任聯合攔截器會攔截全部來自客戶端的請求,並對 http 請求內容進行分析,若是知足 TAI 的認證要求,將認爲用戶是合法的使用者,並將建立一個用戶信息對象傳遞給 WAS 繼續處理,根據應用配置的不一樣,經過 TAI 的認證後展示的內容不一樣。本章將簡單介紹 WAS TAI,重點介紹如何利用 OpenSAML 庫來解析來自客戶端的 SAMLResponse 請求。若是讀者對 TAI 的定製化實現感興趣,能夠參考 IBM 提供的 Websphere Application Server 官方文檔。
WAS TAI 接口的兩個關鍵方法以下:
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 部分 ).
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 的官方站點。