1. 程式人生 > >Spring Security3 使用中心認證服務(CAS)進行單點登入

Spring Security3 使用中心認證服務(CAS)進行單點登入

高階CAS配置

CAS認證框架提供了高階的配置和與CAS服務的資料交換。在本節中,我們將會介紹CAS整合的高階配置。在我們覺得重要的地方將會包含相關的CAS配置指令,但是要記住的是CAS配置是很複雜的並超出了本書的範圍。

從CAS  assertion中獲取屬性

在CAS伺服器傳遞ticket校驗結果時,可以將基於CAS認證時查詢到的資訊進行傳遞(給CAS服務)。這些資訊以鍵值對的方式進行傳遞,並可以包含使用者相關的任何資料。我們將會使用這個功能在CAS響應中傳遞使用者的屬性,包括GrantedAuthority資訊。

CAS內部如何工作

在進入CAS配置之前,我們簡單介紹CAS認證過程的標準行為。下圖將會幫助你理解CAS與嵌入式LDAP伺服器互動的配置步驟:



上圖描述了CAS伺服器內部的認證流程,如果你要實現Spring Security與CAS的整合,你可能要修改CAS伺服器的配置。所以,理解CAS認證的整體流程如何工作是很重要的。

CAS伺服器的org.jasig.cas.authentication.AuthenticationManager(不要與SpringSecurity的同名類相混淆)負責基於提供的憑證資訊進行使用者認證。與Spring Security很相似,實際的認證委託給了一個(或更多)實現了org.jasig.cas.authentication.handler.AuthenticationHandler介面的處理類(在Spring Security中對應的介面是AuthenticationProvider)。

最後,一個org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver用來將傳遞進來的安全實體資訊轉換成完整的org.jasig.cas.authentication.principal.Principal(類似於Spring Security中UserDetailsService實現所作的那樣)。

儘管沒有沒有完整介紹CAS伺服器的完整功能,但是這些也能夠幫助你理解下面幾個練習中的配置步驟。我們建議你閱讀CAS的原始碼並學習網路上的文件,在JA-SIG CAS wiki上,地址為: http://www.ja-sig.org/wiki/display/CAS

配置CAS連線嵌入式LDAP伺服器

CAS預設配置的org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver並不允許我們在與Spring Security CAS整合時,傳遞迴屬性資訊,所以我們建議使用一個允許我們這樣做的實現。

一個很容易配置和使用的認證處理(尤其是你學習了前章中的LDAP練習)是org.jasig.cas.authentication.handler.AuthenticationHandler,它會與我們前章中的嵌入式LDAP伺服器通訊。在後文中,我們將會介紹配置CAS返回LDAP屬性。

所有CAS的配置都會在CAS安裝後的WEB-INF/deployerConfigContext.xml檔案中,將會涉及到插入類宣告到已經存在的配置檔案片段中。

【如果這個檔案的內容你感到熟悉,那是因為CAS使用Spring框架進行配置,就像JBCP Pets!如果你想深入瞭解這些配置是做什麼的,我們建議你使用一個較好的IDE來方便的檢視CAS的原始碼。記住在本節及其餘所有章節中,我們提到的WEB-INF/deployerConfigContext.xml,指的是CAS安裝環境中而不是JBCP Pets。】

首先,我們需要新增一個AuthenticationHandler來代替SimpleTestUsernamePasswordAuthenticationHandler,它將會試圖繫結使用者到LDAP上(就像我們在第九章:LDAP目錄服務中做的那樣)。AuthenticationHandler要放在authenticationManager  bean的authenticationHandlers屬性中:

java程式碼:
  1. <property name="authenticationHandlers">    
  2.   <list>    
  3. <!-- ... -->    
  4.   <bean class="org.jasig.cas.adaptors.ldap.BindLdapAuthenticationHandler">    
  5.     <property name="filter" value="uid=%u" />    
  6.     <property name="searchBase" value="ou=Users,dc=jbcppets,dc=com" />    
  7.     <property name="contextSource" ref="contextSource" />    
  8.   </bean>    

不要忘記移除SimpleTestUsernamePasswordAuthenticationHandler,或者至少將其定義移到BindLdapAuthenticationHandler後面,否則你的CAS認證不會使用LDAP而是會使用這個預設實現。

你可能會意識到這個bean引用了一個contextSource bean——它定義了org.springframework.ldap.core.ContextSource實現,而CAS將會用它與LDAP互動(對,CAS也使用了Spring LDAP)。我們將會在檔案的最後進行定義,如下:

java程式碼:
  1. <bean id="contextSource"class="org.springframework.ldap.core.support.LdapContextSource">    
  2.  <property name="urls">    
  3.   <list>    
  4.    <value>ldap://127.0.0.1:33389</value>  
  5.   </list>    
  6.  </property>    
  7.  <property name="userDn"
  8.  value="uid=ldapadmin,ou=Administrators,ou=Users,dc=jbcppets,dc=com"/>    
  9.  <property name="password" value="password"/>    
  10.  <property name="baseEnvironmentProperties">    
  11.   <map>    
  12.    <entry>    
  13.      <key>    
  14.       <value>java.naming.security.authentication</value>    
  15.      </key>    
  16.      <value>simple</value>    
  17.    </entry>    
  18.   </map>    
  19.  </property>    
  20. </bean>   


與我們配置Spring Security連線外部的(非嵌入式的)LDAP伺服器很類似,CAS需要一個使用者DN作為管理使用者以首先繫結到LDAP目錄上。在本例中,我們使用的管理員是在第九章的JBCPPets.ldif啟動練習中定義的。URL  ldap://127.0.0.1:33389包含了埠33389,是Spring Security嵌入式LDAP伺服器預設使用的。正如我們在第九章討論的,在產品配置中,你可能更會使用LDAPS以確保CAS LDAP請求的安全。

最後,我們需要配置一個新的org.jasig.cas.authentication.principal.CredentialsToPrincipalResolver,它負責將使用者提供的憑證(CAS已經使用BindLdapAuthenticationHandler進行了認證)轉換成完整的org.jasig.cas.authentication.principal.Principal認證實體。你會發現在這個類中有很多配置選項,我們會將其略過,但是歡迎你深入瞭解CAS。

在CAS authenticationManager bean中的credentialsToPrincipalResolvers屬性中,新增如下的內聯bean定義:

java程式碼:
  1. <property name="credentialsToPrincipalResolvers">    
  2.   <list>    
  3.     <bean class="org.jasig.cas.authentication.principal.CredentialsToLDAPAttributePrincipalResolver">    
  4.       <property name="credentialsToPrincipalResolver">    
  5.         <bean class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />    
  6.       </property>    
  7.       <property name="filter" value="(uid=%u)" />    
  8.       <property name="principalAttributeName" value="uid" />    
  9.       <property name="searchBase" value="ou=Users,dc=jbcppets,dc=com" />    
  10.       <property name="contextSource" ref="contextSource" />    
  11.       <property name="attributeRepository" ref bean="attributeRepository" />    
  12.     </bean>    


你會發現就像Spring Security LDAP配置,相同的行為也在CAS中存在即基於DN中的目錄子樹根據屬性匹配搜尋安全實體。

注意,我們還沒有配置attributeRepository,它引用了org.jasig.services.persondir.IpersonAttributeDao的實現。CAS提供了預設的配置,包含了這個介面的簡單實現org.jasig.services.persondir.support.StubPersonAttributeDao,這在LDAP屬性的練習前是足夠的(稍後會講到)。

所以,現在我們已經在CAS中配置了基本的LDAP認證,你能夠重啟CAS,啟動JBCP Pets(如果它沒有在執行的話)並認證任何在第九章中使用的LDAP使用者(使用者名稱ldapguest,密碼password就不錯)。但是在返回應用時,你會看到一個醜陋的403訪問拒絕頁面,這是因為我們的AuthenticationUserDetailsService在資料庫中沒有找到LDAP使用者。現在讓我們來解決這個問題!

從CAS  assertion中獲取UserDetails

當首次建立CAS與Spring Security整合時,我們配置過UserDetailsByNameServiceWrapper,它會將CAS提供的使用者名稱轉換成UserDetails,這是提過使用我們引用的UserDetailsService物件(我們的例子中,就是JdbcDaoImpl)。現在CAS引用了LDAP伺服器,我們可以使用LdapUserDetailsService就像我們在第九章結束時討論的那樣,功能就會好用了。

但是在這裡,我們體驗Spring Security CAS整合的另外一種能力,即從CAS assertion中填充UserDetails。這隻需簡單的將AuthenticationUserDetailsService實現切換至o.s.s.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService就可以了,它的工作就是讀取CAS assertion、尋找特定的屬性並將屬性值與使用者的GrantedAuthority進行匹配。假設assertion返回了一個名為role的屬性。我們只需要在dogstore-base.xml中簡單配置一個新的authenticationUserDetailsService bean:

java程式碼:
  1. <bean id="authenticationUserDetailsService"
  2.  class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">    
  3.   <constructor-arg>    
  4.     <array>    
  5.       <value>role</value>    
  6.     </array>    
  7.   </constructor-arg>    
  8. </bean>   

接下來,要新增一些小的除錯功能來允許我們查詢assertion中的內容。

檢查CAS  assertion

為了輔助查詢CAS返回給JBCP Pets應用的資訊,我們修改AccountController以展現CAS登入使用者的資訊以及CAS為我們提供的使用者資訊。首先,我們新增一個簡單的URL處理方法到AccountController中:

java程式碼:
  1. @RequestMapping(value="/account/viewCasUserProfile.do",method=RequestMethod.GET)    
  2. publicvoid showViewCasUserProfilePage(ModelMap model) {    
  3.   final Authentication auth = SecurityContextHolder.getContext().getAuthentication();    
  4.   model.addAttribute("auth", auth);    
  5.   if(auth instanceof CasAuthenticationToken) {    
  6.     model.addAttribute("isCasAuthentication", Boolean.TRUE);    
  7.   }     
  8. }   

接下來,我們要新增一個JSP,它會展現CasAuthenticationToken的一些資訊,這個物件代表CAS認證過的使用者。它會放在WEB-INF/views/account/viewCasUserProfile.jsp。

java程式碼:
  1. <!-- Common Header and Footer Omitted -->    
  2. <h1>View Profile</h1>    
  3. <p>    
  4.   Some information about you, from CAS:    
  5. </p>    
  6. <ul>    
  7.   <li><strong>Auth:</strong> ${auth}</li>    
  8.   <li><strong>Username:</strong> ${auth.principal}</li>    
  9.   <li><strong>Credentials:</strong> ${auth.credentials}</li>    
  10.   <c:if test="${isCasAuthentication}">    
  11.     <li><strong>Assertion:</strong> ${auth.assertion}</li>    
  12.     <li><strong>Assertion Attributes:</strong>    
  13.     <c:forEach items="${auth.assertion.attributes}" var="attr">    
  14.       ${attr.key}:${attr.value}<br />    
  15.     </c:forEach>    
  16. </li>    
  17. <li><strong>Assertion Attribute Principal:</strong> ${auth.assertion.principal}</li>    
  18. <li><strong>Assertion Principal Attributes:</strong>    
  19.     <c:forEach items="${auth.assertion.principal.attributes}" var="attr">    
  20.       ${attr.key}:${attr.value}<br />    
  21.     </c:forEach>    
  22. </li>    
  23. </c:if>    
  24. </ul>   
在賬號首頁新增一個簡單的連結,在WEB-INF/views/account/home.jsp中: java程式碼:
  1. <h1>Welcome to Your Account</h1>    
  2. <!-- omitted -->    
  3. <ul>    
  4.   <li><a href="viewCasUserProfile.do">View CAS User Profile</a></li>  

最後,在assertion屬性認證好用之前,我們需要(臨時的)將這個頁面進行授權檢查。(Finally, we'll have to (temporarily) disable authorization checks for this page, until we get assertion attribute-based authorization working. )要做到這樣只需要簡單調整dogstore-security.xml,所以任何具有GrantedAuthority的登入使用者都能訪問這個頁面以及“My Account”頁面:

java程式碼:
  1. <intercept-url pattern="/home.do" access="permitAll"/>    
  2. <intercept-url pattern="/account/home.do" access="!anonymous"/>    
  3. <intercept-url pattern="/account/view*Profile.do"
  4. access="!anonymous"/>    
  5. <intercept-url pattern="/account/*.do" access="hasRole('ROLE_USER')"/>    

在完成這些細小的UI更新後,重啟JBCP Pets應用,並試圖訪問這個頁面。你會發現在頁面提供了很多assertion包含的資訊。

匹配LDAP屬性到CAS屬性

最後一個揭開的謎底是需要我們匹配LDAP屬性到CAS assertion中(包括我們希望在GrantedAuthority中包含的role屬性)。

我們將會在CAS deployerConfigContext.xml中新增一點其它的配置。這些新的配置將會指導CAS怎樣從CAS Principal物件到CAS IPersonAttributes物件匹配屬性,而後者將會最終序列化為ticket校驗的一部分。這個bean的配置應該替代同名的bean即attributeRepository。

java程式碼:
  1. <bean id="attributeRepository"
  2.  class="org.jasig.services.persondir.support.ldap.LdapPersonAttributeDao">    
  3.   <property name="contextSource" ref="contextSource" />    
  4.   <property name="requireAllQueryAttributes" value="true" />    
  5.   <property name="baseDN" value="ou=Users,dc=jbcppets,dc=com" />    
  6.   <property name="queryAttributeMapping">    
  7.     <map>    
  8.       <entry key="username" value="uid" />    
  9.     </map>    
  10.   </property>    
  11.   <property name="resultAttributeMapping">    
  12.     <map>    
  13.       <entry key="cn" value="FullName" />    
  14.       <entry key="sn" value="LastName" />    
  15.       <entry key="description" value="role" />    
  16.     </map>    
  17.   </property>    
  18. </bean>    

這個功能可能會很令人迷惑——本質上,這個類的目的將Principal與後端的LDAP目錄進行匹配(這就是queryAttributeMapping屬性,將Principal的username域與LDAP查詢的uid屬性相匹配)。提供的baseDN屬性要使用LDAP進行查詢(uid=ldapguest) ,並且屬性要從匹配的條目進行讀取。 匹配到Principal屬性使用resultAttributeMapping屬性中的鍵值對——我們將LDAP的cn和sn屬性匹配到有意義的名字,而description屬性匹配到role屬性,而這個role屬性就是GrantedAuthorityFromAssertionAttributesUserDetailsService要進行查詢的。

造成這樣的複雜性很大程度上是因為這個功能的一部分被另外一個專案進行了封裝即Person Directory(http://www.ja-sig.org/wiki/display/PD/Home),它的目的是從多個源收集關於某個人的資訊並整合到單個檢視上。Person Directory的設計並沒有直接關聯到CAS伺服器上,所以能夠重用在其它應用中。這種設計選擇的不足之處在於它會使得CAS整合的配置比預想的更復雜。

【一些CAS中已有的LDAP屬性。我們可能願意建立與第九章Spring Security LDAP相同型別的LDAP查詢,即能夠匹配Principal到一個完整的LDAP標識名,而後用這個DN來查詢組資訊(使用基本的uniqueMember屬性到一個groupOfUniqueNames條目)。但是,CAS LDAP程式碼並沒有這種靈活性,這就使得更復雜的LDAP匹配需要擴充套件CAS中的基本類。】

還是感到迷惑?我們承認這對我們來說也有些迷惑,因為在LDAP資料和CAS資料之間的來回互動中有不同的方式。CAS的郵件列表和社群wiki(http://www.ja-sig.org/wiki/display/CASUM/Home)是瞭解別人經驗和與有知識的讀者詢問問題的好地方。

最後,返回CAS  assertion中的屬性

遺憾的是,CAS 2.0協議並沒有明確定義返回資料的標準格式。在CAS的JIRA缺陷跟蹤系統中,有很多嘗試實現這個功能,但是被拒絕了,因為CAS2.0是穩定的所以在最近並不會修改它。

也就是說,很多使用者需要擴充套件CAS的響應來包含屬性。最終,CAS到客戶應用的ticket校驗響應是通過一個JSP渲染的,所以很容易修改你的CAS安裝來包含一個響應,這個響應符合Cas20ServiceTicketValidator的屬性解析。在你的CAS部署中,編輯WEB-INF/view/jsp/protocol/2.0/casServiceValidationSuccess.jsp,並新增以下內容:

java程式碼:
  1. <cas:authenticationSuccess>    
  2.   <cas:user>${fn:escapeXml(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.id)}</cas:user>    
  3.   <cas:attributes>    
  4.   <c:forEach var="attr" items="${assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes}"
  5.   varStatus="loopStatus" begin="0"
  6.   end="${fn:length(assertion.chainedAuthentications[fn:length(assertion.chainedAuthentications)-1].principal.attributes)-1}" step="1">    
  7.   <cas:${fn:escapeXml(attr.key)}>${fn:escapeXml(attr.value)}</cas:${fn:escapeXml(attr.key)}>    
  8.   </c:forEach>    
  9.   </cas:attributes>    

這會產生如下的CAS響應格式:

java程式碼:
  1. <cas:serviceResponse xmlns:cas="http://www.yale.edu/tp/cas">    
  2.   <cas:authenticationSuccess>    
  3.     <cas:user>ldapguest</cas:user>    
  4.     <cas:attributes>    
  5.       <cas:FullName>LDAP Guest</cas:FullName>    
  6.       <cas:role>ROLE_USER</cas:role>    
  7.       <cas:LastName>Guest</cas:LastName>    
  8.     </cas:attributes>    
  9.   </cas:authenticationSuccess>    
  10. </cas:serviceResponse>    

當這些修改完成並重啟CAS和JBCP Pets,你應該能夠用ldapguest登入並訪問使用ROLE_USER保護的區域。另外,你應該看一下“View CAS Profile”頁面,它展現了CAS assertion返回的屬性。

【注意——我們可以看到LdapPersonAttributeDao.resultAttributeMapping提供的屬性名被直接用到CAS響應的XML中。這意味著它們不能是任意的,必須符合XML元素命名規則(例如,不能包含空格)。如果你想修改這種行為,你可能需要比這裡更復雜的JSP程式碼。】

乾的漂亮——在這兩個複雜的產品間有很多的配置工作。休息一下喝杯咖啡吧!

一些CAS開發人員選擇的另一種方式是擴充套件Cas20ServiceTicketValidator(在Spring Security這一端)來對CAS返回的響應進行自定義的處理。

使用SAML 1.1進行替代的票據認證

SAML是一個標準的、跨平臺的協議,它通過結構化的XML assertion進行身份校驗。SAML被很多的產品所支援,包括CAS(實際上,我們將會在稍後的章節中看到Spring Security裡面對SAML的支援)。

SAML的安全assertion XML語法解決了我們前面講到的CAS響應協議傳遞屬性的問題。令人高興的是,切換CAS ticket校驗和SAML ticket校驗只需要修改dogstore-base.xml中的TicketValidator實現即可。新增一個bean如下:

java程式碼:
  1. <bean id="samlTicketValidator"class="org.jasig.cas.client.validation.Saml11TicketValidator">    
  2.   <constructor-arg value="http://localhost:8080/cas/"/>    
  3. </bean>    

然後,替換CasAuthenticationProvider使用這個TicketValidator:

java程式碼:
  1. <bean id="casAuthenticationProvider"
  2.  class="org.springframework.security.cas.authentication.CasAuthenticationProvider">    
  3.   <property name="ticketValidator" ref="samlTicketValidator"/>    
  4.   <property name="serviceProperties" ref="casService"/>    

重啟JBCP Pets應用就會使用SAML響應進行ticket校驗了。注意的是,CAS ticket響應並沒有被Spring Security進行日誌記錄,所以你要麼對JA-SIG CAS客戶端類啟用日誌(在log4j中對org.jasig啟用日誌)要麼使用除錯檢視響應的不同。

總之,建議使用SAML ticket校驗而不是CAS 2.0 ticket校驗,因為前者提供了更多的防治重造的功能,包括時間戳校驗並以標準的方式解決了屬性問題。

屬性查詢的用處

要記住的是CAS為我們的應用提供了一層抽象,移除了我們引用對使用者儲存的直接訪問,作為替代所有這樣的訪問通過CAS作為代理來進行。

這個功能很強大!這意味著我們的應用不再關心使用者在什麼型別的儲存中,也不用關心怎樣訪問它們的細節——只需與CAS確認一個使用者有許可權訪問應用。對於系統管理員來說,這意味著,如果LDAP改名、轉移位置或者修改的話,他們只需要在一個位置進行重新配置——CAS。通過CAS進行集中訪問使得組織中安全架構更高級別的靈活性和適應性成為可能。

擴充套件這個話題到從CAS獲取屬性的用處——現在,所有通過CAS認證的應用對使用者都有了相同的視角(譯者注:即獲取的資訊是一致的),從而能夠在任何使用CAS的環境下以一致的格式顯示資訊。

注意的是,一旦認證完成,Spring Security CAS不會再次查詢CAS除非使用者需要重新認證。這意味著,儲存在應用本地使用者Authentication物件中的屬性和其它使用者資訊可能會失效且可能與CAS伺服器不一致。注意要設定session的超時時間以避免這種潛在的問題。

其它的CAS功能

除了通過Spring Security CAS包裝暴露出來的功能,CAS還提供了高階配置功能。其中一些如下:

   對訪問多個使用CAS安全的應用提供了透明的單點登入功能,這要在一個可配置(在CAS伺服器端)的時間內。通過在TicketValidator中將renew屬性設定為true,應用可以強制使用者到CAS進行認證——你可能希望在自定義程式碼中有條件的設定這個屬性,如當用戶試圖訪問應用中高安全性的區域時;

   為不能直接訪問CAS的二級應用提供了ticket代理功能。當從CAS請求一個ticket時,web應用可能通過二級代理應用來請求授權ticket。關於這個的更多功能可以閱讀CAS網站(http://www.jasig.org/cas/proxy-authentication);

   協調擁有活躍CAS session的多個參與應用間的單點登出(這不是通過Spring Security直接支援的,而是CAS客戶端通過聯合使用HttpSessionListener和servlet過濾器)。

我們建議你去探索CAS客戶端和服務端的所有功能,並在JA-SIG社群論壇中向大家提問題。

小結

在本章中,我們學習了中心認證服務(CAS)單點登入門戶,以及它怎樣與Spring Security整合,在本章中,我們學到了如下內容:

         CAS架構以及在使用CAS的環境中各個參與角色的通訊;

         使用CAS的應用給開發人員和系統管理員帶來的好處;

         配置JBCP Pets與基本的CAS安裝互動;

         更新CAS與LDAP互動並實現LDAP與使用CAS的應用共享資料;

         使用修改過的CAS 2.0協議和工業標準的SAML協議實現與CAS的屬性 交換。

         我們希望本章的內容是關於單點登入的一個有趣開端。在市場上還有很多其它的單點登入系統,它們中大多數是商用的,但是CAS在開源SSO領域無疑是領導者,並且是在任何組織中構建單點登入的絕佳平臺。

在下一章中,我們將會轉回標準認證,但是這次我們將會移除使用者名稱和密碼而是依賴一種新的認證機制。有趣嗎?那就開始吧!