相關閱讀SAML2.0入門指南, 此文中已經介紹了SAML協議的基本信息,今天開始將會為大家詳解OpenSaml——SAML協議的一種開源實現。
What's OpenSAML
OpenSAML是一個便於使用SAML消息的依賴庫,其提供的主要功能包括:
- 創建SAML消息;
- 解析SAML對象並導出為XML格式;
- 簽名和加密;
- 對SAML消息進行編碼並傳輸。
目前OpenSAML庫提供Java和C++實現的版本,需要注意的是OpenSAML雖然多應用用於SSO(單點登錄)的開發中,但是該庫本身不提供任何身份識別和授權的功能,其只是實現對於SAML消息的相關操作而已。
SAML
SAML是一種XML框架用來交換安全信息,其中定義了按照安全規范所需要的通信的協議和格式。
SAML是一種中心化的認證機制,其定義了兩種實體相互通信:
- Service Provider(SP): 向用戶提供正式商業服務的實體,通常需要認證一個用戶的身份;
- Identity Provider(IDP): 提供用戶的身份鑒別,確保用戶是其所聲明的身份;
SAML的重要用途:
- 單點登錄(SSO Single Sign-ON);
- 聯合認證(Federated Identity);
- 在其他架構內使用SAML,比如WS-Security。
更多關於SAML的內容,請參看SAML2.0入門指南
SAML相關定義
1. 斷言(Assertions) 即信息
斷言是在SAML中用來描述認證的對象,其中包括一個用戶在什么時間、以什么方式被認證,同時還可以包括一些擴展信息,比如用戶的Email地址和電話等等。
下面便是一個斷言的實例:
2. 協議(Protocol)即通信
協議規定如何執行不同的行為。這些行為被細化成一些列的Request和Response對象,而在這些請求和相應的對象中包含了行為所特別需要的信息。比如,認證請求協議(AuthnRequest Protocol)就規定了一個SP如何請求去獲得一個被認證的與用戶。
<saml2p:AuthnRequest AssertionConsumerServiceURL=http://localhost:8080/webprofile-refproject/sp/consumer Destination="http://localhost:8080/webprofile-refproject/idp/singleSignOnService" ID="_52c9839568ff2e5a10456dfefaad0555" IssueInstant="2014-05-13T17:34:37.810Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTPArtifact" Version="2.0"> <saml2:Issuer> TestSP </saml2:Issuer> <saml2p:NameID PolicyAllowCreate="true" Format="urn:oasis:names:tc:SAML:2.0:nameid-format:transient"/> <saml2p:RequestedAuthnContext Comparison="minimum"> <saml2:AuthnContextClassRef> urn:oasis:names:tc:SAML:2.0:ac:classes:Password </saml2:AuthnContextClassRef> </saml2p:RequestedAuthnContext> </saml2p:AuthnRequest>
3. 綁定(Binding)即傳輸
綁定定義了SAML信息如何使用通信協議被傳輸的。比如,HTTP重定向綁定,即聲明SAML信息將通過HTTP重定向消息傳輸;再比如SAML SOAP綁定,聲明了通過SOAP來傳遞SAML消息。
配置(Profiles) 即綜合
配置定義了如何組織以上信息,並且在一個更高的層次上描述斷言,協議和綁定如何被使用去解決一個具體情況。比如Web瀏覽器的SSO配置就描述了如何一個用戶使用瀏覽器被認證。
元數據(MetaData)
SAML的元數據是配置數據,其包含關於SAML通信各方的信息,比如通信另一方的ID、Web Service的IP地址、所支持的綁定類型以及通信中實用的密鑰等等。
OpenSaml中提供了metadata provider來幫助構建和解讀元數據。
SAML通過Web瀏覽器實現的協議流程
通過HTTP協議綁定來實現SSO:
1. 用戶嘗試獲得權限
流程首先從一個非認證的用戶開始,該用戶嘗試從一個受保護的SP那里獲得訪問權限。某種方式的過濾器被設置在訪問路徑上來檢測用戶是否被授權(J2ee中servlet類就是一個很好地例子)。這一部分其實並不是SAML協議里的內容,但是卻決定了是否要被授權。
2. 用戶被重定向到IDP
當訪問路徑上被設置的過濾器發現用戶並非是被認證的,將會自動把用戶從定向到IDP,以求驗證用戶的身份。
3. 用戶被認證
在這一步里,用戶被認證。注意這里並沒任何涉及到SP的交互,在安全方式內,IDP對於認證用戶有着全權責任。
4. 已認證的用戶被從定向回SP
當用戶被認證成功之后,用戶會攜帶着SAML產物(SAML artifact)被從定向回SP。這樣的SAML產物也可以說是認證信息的標識,因為認證信息中有敏感的信息不能直接通過瀏覽器傳輸,所以這里只是發送標識而已。
5. 要求認證信息
當收到SAML產物之后,SP將其發送回IDP,IDP依據SAML產物找到認證信息,並通過SAML產物響應(SAML Artifact Response)發送回SP
上面提到的SAML產物響應(SAML Artifact Response) 中就包含SAML斷言,它就是認證的證據。斷言中最重要的數據就該用戶什么時候以什么方式被認證的。
OpenSAML 快速上手
說了這么多理論上的流程,現在開始講講OpenSAML這個庫的構成和使用。
如何添加OpenSAML庫
OpenSAML庫可以在OpenSAML的主頁中獲得,對於Maven用戶可以直接在如下鏈接中獲得依賴:
https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/
OpenSAML3是由Maven組織的多模塊庫,每個模塊的功能各不相同。由於項目功能越來越豐富,對於現在的用戶已經不可能通過一個單一的依賴來引用其所有的功能了。每個模塊都需要添加自己都得引用。OpenSAML最新版的模塊列表如下:
• opensaml-core
• opensaml-profile-api
• opensaml-profile-impl
• opensaml-soap-api
• opensaml-soap-impl
• opensaml-saml-api
• opensaml-saml-impl
• opensaml-xacml-api
• opensaml-xacml-impl
• opensaml-xacml-saml-api
• opensaml-xacml-saml-impl
• opensaml-messaging-api
• opensaml-messaging-impl
• opensaml-storage-api
• opensaml-storage-impl
• opensaml-security-api
• opensaml-security-impl
• opensaml-xmlsec-api
• opensaml-xmlsec-impl
用戶可以根據自己項目的情況自行添加需要的模塊,Maven中具體引用的信息如下:
<dependencies> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-core</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-saml-api</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-saml-impl</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-messaging-api</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-messaging-impl</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-soap-api</artifactId> <version>3.2.0</version> </dependency> <dependency> <groupId>org.opensaml</groupId> <artifactId>opensaml-soap-impl</artifactId> <version>3.2.0</version> </dependency> </dependencies> <repositories> <repository> <id>Shibboleth repo</id> <url> https://build.shibboleth.net/nexus/content/repositories/releases </url> </repository> </repositories>
保證JCE實現的正確性
OpenSAML使用JCE來提供密碼學的功能模塊。由於某些
JCE的實現並不覆蓋所有OpenSAML要求的功能,所以推薦使用Bouncy Castle的JCE實現。
為了幫助用戶來確認JCE的實現是否正確,可以使用如下函數:
JavaCryptoValidationInitializer javaCryptoValidationInitializer = new JavaCryptoValidationInitializer(); javaCryptoValidationInitializer.init();
這個方法應該在OpenSAML初始化之被調用,來確保當前的環境可以符合要求。
如下方法可以用來打印當前已經被安裝的所有JCE的provider:
for (Provider jceProvider : Security.getProviders()) { logger.info(jceProvider.getInfo()); }
使用Maven引用OpenSAML時,Bouncy Castle
provider將會被自動引用。如果是手動下載OpenSAML源碼依賴,其中也已經包括了Bouncy Castle
provider,但是需要手動添加到class path中。
日志打印
OpenSAML使用SLF4J管理日志信息。雖然SLF4J本身沒有任何打印日志的能力,但是其依賴於其他logging的實現來做日志管理。OpenSAML團隊選擇使用Logback來實現logging功能(其他的實現也可以)。
為了能使用LogBack,需要添加依賴包:
- logback-core
- logback-classic
- apache commons logging
其下載鏈接如下:
http://logback.qos.ch/download.html,
https://commons.apache.org/proper/commons-logging/
或者直接添加Maven依賴:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.0.13</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.0.13</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
OpenSAML初始化過程
OpenSAML的初始化依賴於一些列配置文件。OpenSAML已經有一個默認的配置,其已經可以滿足大多數的使用需求,如果有需要還可以對其修改。
配置文件必須在OpenSAML使用之前被加載,加載默認配置需的方法如下進行:
InitializationService.initialize();
這之后,OpenSAML庫才能正常使用;
如果不加載配置文件,OpenSAML的初始化將不能完成,其無法返回某個Obejct,返回NullPointerException。這是一個常見的錯誤。
創建SAML對象
SAML對象的創建使用了工廠模式和構建者模式,涉及到鏈式配置和類型准換。
創建SAML斷言對象的方法如下:
XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); Assertion assertion = (Assertion) builderFactory .getBuilder(Assertion.DEFAULT_ELEMENT_NAME) .buildObject(Assertion.DEFAULT_ELEMENT_NAME);
為了避免大量不需要的代碼,使用泛型的工具方法是一個好主意。可以通過如下方法生成不同類型的對象:
public static <T> T buildSAMLObject(final Class<T> clazz) { XMLObjectBuilderFactory builderFactory = XMLObjectProviderRegistrySupport.getBuilderFactory(); QName defaultElementName = (QName) clazz.getDeclaredField( "DEFAULT_ELEMENT_NAME").get(null); T object = (T) builderFactory.getBuilder(defaultElementName) .buildObject(defaultElementName); return object; }
通過使用上面的方法,就可以將生成斷言對象的代碼簡化為一行;
OpenSAMLUtils.buildSAMLObject(Assertion.class);
實例項目
為了方便讀者理解和后續文章的解讀,這里提供一個示例項目:一個很簡單的網址,其充當SP;同時該項目還包括一個很簡單的IDP;
SAML協議的交互將在這二者之間展開。
項目地址:
https://github.com/sunrongxin7666/OpenSAML-ref-project-demo-v3.git
這個項目基於Bitbucket的一個實驗項目https://bitbucket.org/srasmusson/webprofile-ref-project-v3
其本身是使用Apache Maveng構建的,啟動項目需要執行
mvn tomcat:run
嵌入項目中的Tomcat就會啟動,運行成功時會有如下信息:
INFO: Starting Coyote HTTP/1.1 on http-8080
經過本人的修改,該項目可以在IntelliJ Idea以工程模式打開,運行方式設置為mvn,命令是tomcat:run。這就便於讀者調試和修改。
http://localhost:8080/webprofile-ref-project/app/appservlet
這是一個SP的模擬,第一次訪問該網址時將會跳轉到IDP,進行認證流程。


這雖然是一個最簡單的實例,但是涉及多個部分的代碼,如何使用OpenSAML庫實現每一個步的流程,歡迎關注后續文章:
作者:登高且賦