域對象安全(ACL)
1.1。概觀
複雜應用程序一般會發現須要定義訪問權限,而不只僅是在Web請求或方法調用級別。相反,安全決策須要包括誰(認證),其中(MethodInvocation)和什麼(SomeDomainObject)。換句話說,受權決策還須要考慮方法調用的實際域對象實例主題。javascript
想象你正在爲寵物診所設計一個應用程序。您的春季應用將有兩個主要的用戶羣:寵物診所的工做人員以及寵物診所的客戶。員工將能夠訪問全部的數據,而您的客戶只能看到本身的客戶記錄。爲了使其更有趣,您的客戶能夠容許其餘用戶查看他們的客戶記錄,例如他們的「小狗學前班」導師或當地「小馬俱樂部」的總裁。使用Spring Security做爲基礎,您可使用幾種方法:html
- 編寫您的業務方法來強制執行安全性。您能夠諮詢客戶域對象實例中的集合,以肯定哪些用戶能夠訪問。經過使用SecurityContextHolder.getContext()。getAuthentication(),您將能夠訪問Authentication對象。
- 編寫一個AccessDecisionVoter,以便從存儲在Authentication對象中的GrantedAuthority []執行安全性。這將意味着您的AuthenticationManager須要使用自定義GrantedAuthority []來填充身份驗證,表明主體能夠訪問的每一個Customer域對象實例。
- 編寫一個AccessDecisionVoter來強制執行安全性,並直接打開目標客戶域對象。這意味着您的選民須要訪問容許其檢索Customer對象的DAO。而後它將訪問客戶對象的批准用戶的集合並作出適當的決定。
這些方法中的每一種都是徹底合法的。可是,首先將您的受權檢查與您的業務代碼相結合。其中的主要問題包括增長單元測試的難度,以及在其餘地方重複使用客戶受權邏輯將更加困難。從Authentication對象獲取GrantedAuthority [] s也很好,但不會擴展到大量的Customer。若是用戶能夠訪問5,000個客戶(在這種狀況下不太可能,可是想象一下,若是它是一個大型的小馬俱樂部的受歡迎的獸醫!),構建Authentication對象所需的內存消耗量和時間將是不合須要的。最終的方法,直接從外部代碼打開客戶,多是三個中最好的。它實現了關注點的分離,而且不會濫用內存或CPU週期,可是因爲AccessDecisionVoter和最終業務方法自己都將執行對負責檢索Customer對象的DAO的調用,所以仍然沒有效率。每一個方法調用兩次訪問顯然是不但願的。另外,列出全部方法,您將須要從頭開始編寫本身的訪問控制列表(ACL)持久性和業務邏輯。java
幸運的是,還有另外一種選擇,下面咱們來討論一下。web
1.2。關鍵概念
Spring Security的ACL服務在spring-security-acl-xxx.jar中發佈。您將須要將此JAR添加到您的類路徑中以使用Spring Security的域對象實例安全功能。ajax
Spring Security的域對象實例安全功能集中在訪問控制列表(ACL)的概念上。系統中的每一個域對象實例都有本身的ACL,而且ACL記錄誰能夠和不能與該域對象一塊兒使用的詳細信息。考慮到這一點,Spring Security爲您的應用程序提供三種與ACL相關的主要功能:正則表達式
- 一種有效地檢索全部域對象的ACL條目的方法(並修改這些ACL)
- 在調用方法以前,確保給定主體的方法容許與對象一塊兒工做
- 在調用方法以後,確保給定委託人的方式容許與對象(或其返回的東西)一塊兒工做
如第一個要點所示,Spring Security ACL模塊的主要功能之一是提供檢索ACL的高性能方法。這種ACL存儲庫功能是很是重要的,由於系統中的每一個域對象實例可能具備多個訪問控制條目,而且每一個ACL可能會以相似樹狀的結構從其餘ACL繼承(Spring支持即開即用安全,是很是經常使用的)。 Spring Security的ACL功能通過精心設計,能夠提供ACL的高性能檢索,以及可插拔緩存,死鎖 - 最小化數據庫更新,獨立於ORM框架(咱們直接使用JDBC),適當封裝和透明數據庫更新。算法
給定的數據庫是ACL模塊操做的核心,讓咱們來探索實現中默認使用的四個主表。下表按照典型的Spring Security ACL部署中的大小順序顯示,最後列出最多行的表:spring
- ACL_SID容許咱們惟一地標識系統中的任何主體或權限(「SID」表示「安全身份」)。惟一的列是ID,SID的文本表示,以及用於指示文本表示是指主體名稱仍是GrantedAuthority的標誌。所以,每一個惟一主體或GrantedAuthority都有一行。當在接收權限的上下文中使用時,SID一般稱爲「收件人」。
- ACL_CLASS容許咱們惟一地標識系統中的任何域對象類。惟一的列是ID和Java類名。所以,咱們但願存儲ACL權限的每一個惟一的類都有一行。
- ACL_OBJECT_IDENTITY存儲系統中每一個惟一域對象實例的信息。列包括ID,ACL_CLASS表的外鍵,惟一標識符,所以咱們知道咱們提供哪些ACL_CLASS實例,父級爲ACL_SID表的外鍵以表示域對象實例的全部者,以及是否容許ACL條目從任何父ACL繼承。對於咱們存儲ACL權限的每一個域對象實例,咱們都有一行。
- 最後,ACL_ENTRY存儲分配給每一個收件人的各個權限。列包括ACL_OBJECT_IDENTITY的外鍵,收件人(即ACL_SID的外鍵),是否進行審計,以及表示授予或拒絕的實際權限的整數位掩碼。對於接收到域對象使用權限的每一個收件人,咱們都有一行。
如最後一段所述,ACL系統使用整數位掩碼。不用擔憂,您不須要知道使用ACL系統的位移更精細的點,但足以說咱們能夠打開或關閉32位。這些位中的每個表示許可,默認狀況下,讀取(位0),寫入(位1),建立(位2),刪除(位3)和管理(位4)。若是您但願使用其餘權限,您能夠輕鬆實現本身的權限實例,而其他的ACL框架將在不瞭解擴展的狀況下運行。數據庫
重要的是要了解,系統中的域對象數量對咱們選擇使用整數位掩碼的事實絕對沒有影響。雖然您有32位可用權限,您能夠擁有數十億個域對象實例(這將意味着ACL_OBJECT_IDENTITY中的數十億行,極可能是ACL_ENTRY)。咱們提出這一點,由於咱們發現有時人們錯誤地認爲他們須要一些每一個潛在的域對象,而不是這樣。apache
如今咱們已經提供了ACL系統的基本概述,以及表格結構的外觀,咱們來探討關鍵接口。關鍵接口是:
- Acl:每一個域對象都有一個且只有一個Acl對象,它們內部持有AccessControlEntry,而且知道Acl的全部者。 Acl不直接引用域對象,而是引用ObjectIdentity。 Acl存儲在ACL_OBJECT_IDENTITY表中。
- AccessControlEntry:Acl擁有多個AccessControlEntry,它們一般在框架中縮寫爲ACE。每一個ACE指的是「Permission」,Sid和Acl的特定元組。 ACE還能夠授予或不授予幷包含審覈設置。 ACE存儲在ACL_ENTRY表中。
- Permission:一個權限表明一個不可變的位掩碼,爲位掩碼和輸出信息提供了便利的功能。上面提供的基本權限(位0到4)包含在BasePermission類中。
- Sid:ACL模塊須要引用principal和GrantedAuthority []。間接級別由Sid接口提供,Sid接口是「安全身份」的縮寫。普通類包括PrincipalSid(用於表示Authentication對象中的主體)和GrantedAuthoritySid。安全身份信息存儲在ACL_SID表中。
- ObjectIdentity:每一個域對象在ACL模塊內部由ObjectIdentity表示。默認實現稱爲ObjectIdentityImpl。
- AclService:檢索適用於給定ObjectIdentity的Acl。在包含的實現(JdbcAclService)中,檢索操做被委派給LookupStrategy。 LookupStrategy提供了一個高度優化的策略,用於檢索ACL信息,使用批量檢索(BasicLookupStrategy),並支持利用物化視圖,層次查詢和相似以性能爲中心的非ANSI SQL功能的自定義實現。
- MutableAclService:容許修改的Acl被顯示爲持久性。若是您不但願使用此界面不是必需的。
請注意,咱們即時的AclService和相關數據庫類都使用ANSI SQL。所以,這應與全部主要數據庫配合使用。在撰寫本文時,系統已經使用Hypersonic SQL,PostgreSQL,Microsoft SQL Server和Oracle成功測試。
兩個示例隨Spring Security一塊兒發佈,演示了ACL模塊。第一個是聯繫人樣本,另外一個是文檔管理系統(DMS)示例。咱們建議您查看這些例子。
1.3。入門
要開始使用Spring Security的ACL功能,您須要將ACL信息存儲在某個地方。這須要使用Spring實例化一個DataSource。而後將DataSource注入到JdbcMutableAclService和BasicLookupStrategy實例中。後者提供了高性能的ACL檢索功能,前者提供了mutator功能。請參閱Spring Security附帶的示例配置示例。您還須要使用上一節中列出的四個特定於ACL的表來填充數據庫(請參閱相應SQL語句的ACL示例)。
一旦建立了所需的模式和實例化的JdbcMutableAclService,您將須要確保您的域模型支持與Spring Security ACL軟件包的互操做性。但願ObjectIdentityImpl證實是足夠的,由於它提供了大量可使用的方式。大多數人將擁有包含公共Serializable getId()方法的域對象。若是返回類型很長,或者與long(例如int)兼容,則您將發現不須要進一步考慮ObjectIdentity問題。 ACL模塊的許多部分依賴於長標識符。若是你不使用long(或int,byte等),那麼頗有可能須要從新實現一些類。咱們不打算在Spring Security的ACL模塊中支持非長標識符,由於長期以來已經與全部數據庫序列(最多見的標識符數據類型)兼容,而且具備足夠的長度來適應全部常見的使用場景。
如下代碼片斷顯示瞭如何建立Acl或修改現有的「Acl」:
// Prepare the information we'd like in our access control entry (ACE) ObjectIdentity oi = new ObjectIdentityImpl(Foo.class, new Long(44)); Sid sid = new PrincipalSid("Samantha"); Permission p = BasePermission.ADMINISTRATION; // Create or update the relevant ACL MutableAcl acl = null; try { acl = (MutableAcl) aclService.readAclById(oi); } catch (NotFoundException nfe) { acl = aclService.createAcl(oi); } // Now grant some permissions via an access control entry (ACE) acl.insertAce(acl.getEntries().length, p, sid, true); aclService.updateAcl(acl);
在上面的示例中,咱們檢索與「Foo」域對象相關聯的ACL,標識符爲44。而後咱們添加一個ACE,以便名爲「Samantha」的主體能夠「管理」該對象。代碼片斷相對不言自明,除了insertAce方法。 insertAce方法的第一個參數是肯定Acl中什麼位置將插入新條目。在上面的示例中,咱們只是將新的ACE放在現有ACE的末尾。最後一個參數是一個布爾值,表示ACE是授予仍是拒絕。大多數時候它會被授予(true),可是若是它是拒絕(false),權限被有效地阻止。
Spring Security不提供任何特殊的集成來自動建立,更新或刪除ACL做爲DAO或存儲庫操做的一部分。相反,您須要爲各個域對象編寫如上所示的代碼。值得考慮的是在服務層上使用AOP來自動將ACL信息與服務層操做集成在一塊兒。過去咱們發現這是一個很是有效的方法。
一旦您使用上述技術在數據庫中存儲一些ACL信息,下一步是實際使用ACL信息做爲受權決策邏輯的一部分。你在這裏有不少選擇。您能夠編寫本身的AccessDecisionVoter或AfterInvocationProvider,分別在方法調用以前或以後觸發。這樣的類將使用AclService來檢索相關的ACL,而後調用Acl.isGranted(Permission []權限,Sid [] sids,boolean administrativeMode)來決定是否授予或拒絕權限。或者,您可使用咱們的AclEntryVoter,AclEntryAfterInvocationProvider或AclEntryAfterInvocationCollectionFilteringProvider類。全部這些類都提供了基於聲明的方法來在運行時評估ACL信息,從而無需編寫任何代碼。請參考示例應用程序瞭解如何使用這些類。
2.認證前方案
在某些狀況下,您但願使用Spring Security進行受權,但在訪問應用程序以前,用戶已經被某些外部系統可靠地進行身份驗證。咱們將這些狀況稱爲「預認證」情景。示例包括X.509,Siteminder和由應用程序運行的Java EE容器進行身份驗證。當使用預認證時,Spring Security必須
- 識別發出請求的用戶。
- 獲取用戶的權限。
細節將取決於外部認證機制。在X.509的狀況下,用戶可能會經過其證書信息來標識用戶,或者在Siteminder的狀況下能夠經過HTTP請求標頭識別用戶。若是依賴容器認證,則經過在傳入的HTTP請求上調用getUserPrincipal()方法來識別用戶。在某些狀況下,外部機制可能爲用戶提供角色/權限信息,但在其餘狀況下,必須從單獨的來源(如UserDetailsService)獲取權限。
2.1。預認證框架類
因爲大多數預認證機制遵循相同的模式,Spring Security具備一組類,它們爲實現預認證的認證提供程序提供內部框架。這消除了重複,並容許以結構化的方式添加新的實現,而無需從頭開始編寫全部內容。若是您想使用像X.509認證這樣的一些類,那麼您就不須要了解這些類,由於它已經具備一個更簡單易用的入門命名空間配置選項。若是您須要使用顯式bean配置或正在計劃編寫本身的實現,那麼瞭解提供的實現如何工做將是有用的。你將在org.springframework.security.web.authentication.preauth下找到類。咱們只是在這裏提供一個大綱,因此你應該在適當的時候參考Javadoc和source。
2.1.1。 AbstractPreAuthenticatedProcessingFilter
該類將檢查安全上下文的當前內容,若是爲空,它將嘗試從HTTP請求中提取用戶信息並將其提交給AuthenticationManager。子類覆蓋如下方法來獲取此信息:
protected abstract Object getPreAuthenticatedPrincipal(HttpServletRequest request); protected abstract Object getPreAuthenticatedCredentials(HttpServletRequest request);
調用這些以後,過濾器將建立一個PreAuthenticatedAuthenticationToken,其中包含返回的數據並將其提交以進行身份驗證。經過這裏的「認證」,咱們真的只是意味着進一步處理可能加載用戶的權限,但遵循標準的Spring Security身份驗證架構。
像其餘Spring Security認證過濾器同樣,預認證過濾器具備authenticationDetailsSource屬性,默認狀況下將建立一個WebAuthenticationDetails對象,以將其餘信息(如會話標識符和始發IP地址)存儲在Authentication對象的詳細信息屬性中。在能夠從預認證機制獲取用戶角色信息的狀況下,數據也存儲在此屬性中,具體實現GrantedAuthoritiesContainer接口。這使得認證提供商可以讀取外部分配給用戶的權限。接下來咱們來看一個具體的例子。
J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource
若是過濾器配置了做爲此類的實例的authenticationDetailsSource,則經過爲預約義的「可映射角色」集合中的每個調用isUserInRole(String role)方法來獲取權限信息。該類從配置的MappableAttributesRetriever獲取這些。可能的實現包括對應用程序上下文中的列表進行硬編碼,並從web.xml文件中的<security-role>信息讀取角色信息。預認證示例應用程序使用後一種方法。
還有一個額外的階段,角色(或屬性)使用配置的Attributes2GrantedAuthoritiesMapper映射到Spring Security GrantedAuthority對象。默認值將只是添加一般的ROLE_前綴到名稱,但它可讓你徹底控制行爲。
2.1.2。 PreAuthenticatedAuthenticationProvider
預先認證的提供商比爲用戶加載UserDetails對象要作的更多。它經過委託給AuthenticationUserDetailsService來實現。後者相似於標準UserDetailsService,但採用Authentication對象,而不只僅是用戶名:
public interface AuthenticationUserDetailsService { UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException; }
該接口可能還有其餘用途,可是經過預認證,它容許訪問已打包在Authentication對象中的權限,正如咱們在上一節中所看到的那樣。 PreAuthenticatedGrantedAuthoritiesUserDetailsService類執行此操做。或者,它能夠經過UserDetailsByNameServiceWrapper實現委託給標準UserDetailsService。
2.1.3。 Http403ForbiddenEntryPoint
在技術概述一章中討論了AuthenticationEntryPoint。一般,它負責啓動未認證用戶的身份驗證過程(當他們嘗試訪問受保護的資源時),可是在預認證的狀況下,這不適用。若是不使用預認證與其餘身份驗證機制結合使用,則只能將ExceptionTranslationFilter配置爲此類的實例。若是用戶被AbstractPreAuthenticatedProcessingFilter拒絕,將致使空值認證。若是調用,它老是返回一個403禁止的響應代碼。
2.2。具體實施
X.509認證在其本身的章節中介紹。在這裏,咱們將看一些對其餘預認證場景提供支持的類。
2.2.1。請求頭認證(Siteminder)
外部認證系統能夠經過在HTTP請求上設置特定的頭部來嚮應用程序提供信息。一個衆所周知的例子是Siteminder,它將用戶名傳遞給名爲SM_USER的頭文件。該機制由RequestHeaderAuthenticationFilter類支持,它只是從頭中提取用戶名。它默認使用名稱SM_USER做爲標題名稱。有關詳細信息,請參閱Javadoc。
請注意,當使用這樣的系統時,框架根本不執行身份驗證檢查,外部系統配置正確並保護對應用程序的全部訪問很是重要。 若是攻擊者可以在原始請求中僞造頭部,而不會被檢測到,那麼他們可能會選擇他們但願的任何用戶名。
Siteminder示例配置
使用此過濾器的典型配置以下所示:
<security:http> <!-- Additional http configuration omitted --> <security:custom-filter position="PRE_AUTH_FILTER" ref="siteminderFilter" /> </security:http> <bean id="siteminderFilter" class="org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter"> <property name="principalRequestHeader" value="SM_USER"/> <property name="authenticationManager" ref="authenticationManager" /> </bean> <bean id="preauthAuthProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider"> <property name="preAuthenticatedUserDetailsService"> <bean id="userDetailsServiceWrapper" class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <property name="userDetailsService" ref="userDetailsService"/> </bean> </property> </bean> <security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="preauthAuthProvider" /> </security:authentication-manager>
咱們在這裏假設安全命名空間正在用於配置。 還假定您已將UserDetailsService(稱爲「userDetailsService」)添加到配置中以加載用戶的角色。
2.2.2。 Java EE容器認證
J2eePreAuthenticatedProcessingFilter類將從HttpServletRequest的userPrincipal屬性中提取用戶名。 使用此過濾器一般將與J2EEBasedPreAuthenticatedWebAuthenticationDetailsSource中所述的使用Java EE角色相結合。
在代碼庫中有一個示例應用程序,它使用這種方法,因此從subversion獲得代碼,若是你有興趣,看看應用程序上下文文件。 代碼在samples / preauth目錄中。
3. LDAP認證
3.1。概觀
企業一般使用LDAP做爲用戶信息和認證服務的中央存儲庫。它還能夠用於存儲應用程序用戶的角色信息。
對於如何配置LDAP服務器有許多不一樣的場景,所以Spring Security的LDAP提供程序是徹底可配置的。它使用單獨的策略接口進行身份驗證和角色檢索,並提供可配置爲處理各類狀況的默認實現。
嘗試使用Spring Security以前,您應該熟悉LDAP。如下連接提供了有關概念的良好介紹,以及使用免費LDAP服務器OpenLDAP設置目錄的指南:http://www.zytrax.com/books/ldap/。熟悉Java中用於訪問LDAP的JNDI API也頗有用。咱們不會在LDAP提供程序中使用任何第三方LDAP庫(Mozilla,JLDAP等),但普遍使用Spring LDAP,所以若是您計劃添加本身的自定義項,那麼熟悉該項目可能會頗有用。
使用LDAP身份驗證時,請務必確保正確配置LDAP鏈接池。若是您不熟悉如何執行此操做,能夠參考Java LDAP文檔。
3.2。使用LDAP與Spring Security
Spring Security中的LDAP認證大體分爲如下幾個階段。
- 從登陸名獲取惟一的LDAP「可分辨名稱」或DN。這一般意味着在目錄中執行搜索,除非提早知道用戶名到DN的確切映射。所以,用戶登陸時可能輸入名稱「joe」,但用於對LDAP進行身份驗證的實際名稱將是完整的DN,例如`uid = joe,ou = users,dc = springsource,dc = com`。
- 經過「綁定」做爲該用戶,或者經過對DN的目錄條目中的password屬性進行用戶密碼的遠程「比較」操做來認證用戶。
- 加載用戶的權限列表。
例外狀況是LDAP目錄僅用於檢索用戶信息並在本地進行身份驗證。這多是不可能的,由於目錄一般被設置爲對用戶密碼等屬性的讀取訪問有限。
咱們將在下面看一些配置方案。有關可用配置選項的完整信息,請參閱安全命名空間架構(XML編輯器中應提供的信息)。
3.3。 配置LDAP服務器
您須要作的第一件事是配置服務器,以便進行認證。 這是使用安全命名空間中的<ldap-server>元素完成的。 可使用url屬性將其配置爲指向外部LDAP服務器:
<ldap-server url="ldap://springframework.org:389/dc=springframework,dc=org" />
3.3.1。使用嵌入式測試服務器
<ldap-server>元素也可用於建立嵌入式服務器,這對於測試和演示很是有用。在這種狀況下,您使用它沒有url屬性:
<ldap-server root="dc=springframework,dc=org"/>
這裏咱們指定目錄的根DIT應爲「dc = springframework,dc = org」,這是默認值。使用這種方式,命名空間解析器將建立一個嵌入式Apache目錄服務器,並掃描任何LDIF文件的類路徑,它將嘗試加載到服務器中。您可使用ldif屬性來自定義此行爲,該屬性定義要加載的LDIF資源:
<ldap-server ldif="classpath:users.ldif" />
這使得使用LDAP更容易啓動和運行,由於使用外部服務器可能不方便地工做。它還使用戶不須要鏈接Apache Directory服務器所需的複雜bean配置。使用普通的Spring Bean,配置會更加混亂。您必須具備必需的Apache目錄依賴項可用於您的應用程序使用。這些能夠從LDAP示例應用程序得到。
3.3.2。使用綁定身份驗證
這是最多見的LDAP身份驗證方案。
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people"/>
這個簡單的例子將經過在提供的模式中替換用戶登陸名稱並嘗試用該用戶綁定登陸密碼來獲取用戶的DN。若是您的全部用戶都存儲在目錄中的單個節點下,則此操做是正常的。若是您想要配置LDAP搜索過濾器來定位用戶,則可使用如下內容:
<ldap-authentication-provider user-search-filter="(uid={0})" user-search-base="ou=people"/>
若是與上述服務器定義一塊兒使用,則將使用用戶搜索過濾器屬性的值做爲過濾器執行DN ou = people,dc = springframework,dc = org下的搜索。再次,用戶登陸名替換過濾器名稱中的參數,所以它將搜索uid屬性等於用戶名的條目。若是不提供用戶搜索庫,則搜索將從根執行。
3.3.3。 裝載Authorities
如何從LDAP目錄中的組加載權限由如下屬性控制。
- group-search-base 定義目錄樹中應執行組搜索的部分。
- group-role-attribute 包含由組條目定義的權限的名稱的屬性。 默認爲`cn`
- group-search-filter 用於搜索組成員資格的過濾器。 默認值爲`uniqueMember = {0},對應於`groupOfUniqueNames LDAP類[21]。 在這種狀況下,替換參數是用戶的完整可分辨名稱。 若是要對登陸名進行過濾,可使用參數{1}。
若是咱們使用如下配置
<ldap-authentication-provider user-dn-pattern="uid={0},ou=people" group-search-base="ou=groups" />
做爲用戶「ben」成功進行身份驗證,隨後加載的權限將在目錄條目`you = groups,dc = springframework,dc = org`下執行搜索,尋找包含值爲uid=ben,ou=people,dc=springframework,dc=org。默認狀況下,權限名稱將具備前綴ROLE_前綴。您可使用role-prefix屬性更改它。若是您不想要任何前綴,請使用role-prefix =「none」。有關加載權限的更多信息,請參閱DefaultLdapAuthoritiesPopulator類的Javadoc。
3.4。實施類
咱們上面使用的命名空間配置選項使用簡單,比使用Spring beans明確更簡潔。有時您可能須要知道如何在應用程序上下文中直接配置Spring Security LDAP。例如,您可能但願自定義某些類的行爲。若是你很高興使用命名空間配置,那麼你能夠跳過這個部分和下一個。
主要的LDAP提供程序類LdapAuthenticationProvider實際上並無作太多的工做,可是將工做委託給另外兩個bean,一個LdapAuthenticator和一個LdapAuthoritiesPopulator,它們分別負責驗證用戶和檢索用戶的一組GrantedAuthority。
3.4.1。 LdapAuthenticator實現
認證者還負責檢索任何所需的用戶屬性。這是由於對屬性的權限可能取決於正在使用的身份驗證的類型。例如,若是做爲用戶綁定,可能須要用用戶本身的權限讀取它們。
目前,Spring Security提供了兩種認證策略:
- 直接認證到LDAP服務器(「綁定」身份驗證)。
- 密碼比較,將用戶提供的密碼與儲存庫中存儲的密碼進行比較。這能夠經過檢索password屬性的值並在本地進行檢查或經過執行LDAP「比較」操做來完成,其中提供的密碼被傳遞到服務器進行比較,而且永遠不會檢索真實的密碼值。
共同功能
在能夠對用戶進行身份驗證(經過任一策略)以前,必須從提供給應用程序的登陸名獲取可分辨名稱(DN)。這能夠經過簡單的模式匹配(經過設置setUserDnPatterns數組屬性)或經過設置userSearch屬性來完成。對於DN模式匹配方法,使用標準Java模式格式,登陸名將替代參數{0}。該模式應與配置的SpringSecurityContextSource將綁定的DN相關(有關此服務器的更多信息,請參閱鏈接到LDAP服務器的部分)。例如,若是您使用具備URL`ldap:// monkeymachine.co.uk / dc = springframework,dc = org`的LDAP服務器,而且具備模式uid = {0},則ou = greatapes,而後登陸「大猩猩」的名稱將映射到DN`uid = gorilla,ou = greatapes,dc = springframework,dc = org`。每一個配置的DN模式將依次嘗試,直到找到匹配項。有關使用搜索的信息,請參閱下面的搜索對象部分。也可使用兩種方法的組合 - 首先檢查模式,而且若是找不到匹配的DN,則將使用搜索。
認證者
包org.springframework.security.ldap.authentication中的BindAuthenticator類實現了綁定身份驗證策略。它只是嘗試做爲用戶綁定。
PasswordComparisonAuthenticator
PasswordComparisonAuthenticator類實現密碼比較認證策略。
3.4.2。鏈接到LDAP服務器
上面討論的bean必須可以鏈接到服務器。它們都必須提供SpringSecurityContextSource,它是Spring LDAP的ContextSource的擴展。除非有特殊要求,不然您一般會配置一個DefaultSpringSecurityContextSource bean,該Bean可使用LDAP服務器的URL進行配置,而且還可使用默認狀況下綁定到服務器時使用的「管理員」用戶名和密碼(而不是匿名綁定)。有關更多信息,請閱讀此類的Javadoc和Spring LDAP的AbstractContextSource。
3.4.3。 LDAP搜索對象
一般須要比簡單的DN匹配更復雜的策略來定位目錄中的用戶條目。這能夠封裝在LdapUserSearch實例中,該實例能夠提供給認證者實現,例如容許他們定位用戶。提供的實現是FilterBasedLdapUserSearch。
FilterBasedLdapUserSearch中
此bean使用LDAP篩選器來匹配目錄中的用戶對象。該過程在Javadoc中對相應的搜索方法進行了說明
thehttp://java.sun.com/j2se/1.4.2/docs/api/javax/naming/directory/DirContext.html#search(javax.naming.Name,%20java.lang.String,%20java.lang.Object[],%20javax.naming.directory.SearchControls)[JDK DirContext class]. As explained there, the search filter can be supplied with parameters. For this class, the only valid parameter is {0}
which will be replaced with the user’s login name.
3.4.4。LdapAuthoritiesPopulator在
成功驗證用戶後,LdapAuthenticationProvider將嘗試經過調用配置的LdapAuthoritiesPopulator bean來爲用戶加載一組權限。 DefaultLdapAuthoritiesPopulator是一個實現,它將經過在目錄中搜索用戶所屬的組(一般這些將是目錄中的groupOfNames或groupOfUniqueNames條目)來加載權限。 有關此類的Javadoc詳細信息,請參考Javadoc。
若是要僅使用LDAP進行身份驗證,而是從差別源(如數據庫)加載權限,那麼您能夠提供本身的該接口的實現並注入該接口。
3.4.5。 Spring Bean配置
使用咱們在這裏討論的一些bean的典型配置可能以下所示:
<bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource"> <constructor-arg value="ldap://monkeymachine:389/dc=springframework,dc=org"/> <property name="userDn" value="cn=manager,dc=springframework,dc=org"/> <property name="password" value="password"/> </bean> <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider"> <constructor-arg> <bean class="org.springframework.security.ldap.authentication.BindAuthenticator"> <constructor-arg ref="contextSource"/> <property name="userDnPatterns"> <list><value>uid={0},ou=people</value></list> </property> </bean> </constructor-arg> <constructor-arg> <bean class="org.springframework.security.ldap.userdetails.DefaultLdapAuthoritiesPopulator"> <constructor-arg ref="contextSource"/> <constructor-arg value="ou=groups"/> <property name="groupRoleAttribute" value="ou"/> </bean> </constructor-arg> </bean>
這將設置提供者使用URL ldap:// monkeymachine:389 / dc = springframework,dc = org訪問LDAP服務器。 將經過嘗試與DN`uid = <user-login-name>,ou = people,dc = springframework,dc = org`進行綁定來執行身份驗證。 成功認證後,經過使用默認過濾器(member = <user's-DN>)在DN ou = groups,dc = springframework,dc = org下搜索角色將分配給用戶。 角色名稱將取自每場比賽的「ou」屬性。
要配置使用過濾器(uid = <user-login-name>)而不是DN模式(或除此以外)的用戶搜索對象,您將配置如下bean
<bean id="userSearch" class="org.springframework.security.ldap.search.FilterBasedLdapUserSearch"> <constructor-arg index="0" value=""/> <constructor-arg index="1" value="(uid={0})"/> <constructor-arg index="2" ref="contextSource" /> </bean>
並經過設置BindAuthenticator bean的userSearch屬性來使用它。 而後,在嘗試綁定該用戶以前,認證器將調用搜索對象以獲取正確的用戶DN。
3.4.6。 LDAP屬性和自定義用戶詳細信息
使用LdapAuthenticationProvider的身份驗證的最終結果與使用標準UserDetailsService接口的普通Spring Security身份驗證相同。 UserDetails對象被建立並存儲在返回的Authentication對象中。 與使用UserDetailsService同樣,一般的要求是可以自定義此實現並添加額外的屬性。 當使用LDAP時,這些一般是來自用戶條目的屬性。 UserDetails對象的建立由提供商的UserDetailsContextMapper策略控制,該策略負責將用戶對象映射到LDAP上下文數據:
public interface UserDetailsContextMapper { UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<GrantedAuthority> authorities); void mapUserToContext(UserDetails user, DirContextAdapter ctx); }
只有第一種方法與認證有關。若是您提供此接口的實現並將其注入到LdapAuthenticationProvider中,則能夠精確控制如何建立UserDetails對象。第一個參數是Spring LDAP的DirContextOperations的一個實例,它容許您訪問在身份驗證期間加載的LDAP屬性。 username參數是用於驗證的名稱,最終參數是由configure.LdapAuthoritiesPopulator爲用戶加載的權限集合。
加載上下文數據的方式會根據您使用的身份驗證類型略有不一樣。使用BindAuthenticator,從綁定操做返回的上下文將用於讀取屬性,不然將使用從配置的ContextSource獲取的標準上下文讀取數據(當搜索配置爲定位用戶時,這將是數據由搜索對象返回)。
3.5。 Active Directory身份驗證
Active Directory支持其本身的非標準身份驗證選項,而且正常使用模式與標準的LdapAuthenticationProvider不太乾淨。一般,使用域用戶名(用戶@域名)執行身份驗證,而不是使用LDAP可分辨名稱。爲了使這更容易,Spring Security 3.1有一個身份驗證提供程序,爲一個典型的Active Directory設置定製。
3.5.1。 ActiveDirectoryLdapAuthenticationProvider
配置ActiveDirectoryLdapAuthenticationProvider是很是簡單的。您只須要提供域名和提供服務器地址的LDAP URL [22]。而後,示例配置將以下所示:
<bean id="adAuthenticationProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider"> <constructor-arg value="mydomain.com" /> <constructor-arg value="ldap://adserver.mydomain.com/" /> </bean>
請注意,爲了定義服務器位置,不須要指定單獨的ContextSource - 該bean是徹底獨立的。例如,名爲「Sharon」的用戶將可以經過輸入用戶名sharon或完整的Active Directory userPrincipalName(即sharon@mydomain.com)進行身份驗證。而後將定位用戶的目錄條目,而且返回的屬性可用於自定義建立的UserDetails對象(如上所述,能夠爲此目的注入UserDetailsContextMapper)。與目錄的全部交互都與用戶自己的身份進行。沒有一個「經理」用戶的概念。
默認狀況下,用戶權限是從用戶條目的memberOf屬性值得到的。分配給用戶的權限能夠再次使用UserDetailsContextMapper進行定製。您還能夠將GrantedAuthoritiesMapper注入到提供者實例中,以控制身份驗證對象中的權限。
Active Directory錯誤代碼
默認狀況下,失敗的結果將致使標準的Spring Security BadCredentialsException。若是將屬性convertSubErrorCodesToExceptions設置爲true,則將解析異常消息以嘗試提取Active Directory特定的錯誤代碼並提出更具體的異常。查看類Javadoc瞭解更多信息。
4. JSP標籤庫
Spring Security擁有本身的taglib,它爲訪問安全信息和在JSP中應用安全約束提供了基本的支持。
4.1。聲明Taglib
要使用任何標籤,您必須在JSP中聲明安全性標籤lib:
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
4.2。 受權標籤
該標籤用於肯定其內容是否應該被評估。 在Spring Security 3.0中,它能夠以兩種方式使用[23]。 第一種方法使用在標籤的訪問屬性中指定的Web安全性表達式。 表達式評估將被委派給應用程序上下文中定義的SecurityExpressionHandler <FilterInvocation>(您應該在<http>命名空間配置中啓用Web表達式以確保此服務可用)。 例如
<sec:authorize access="hasRole('supervisor')"> This content will only be visible to users who have the "supervisor" authority in their list of <tt>GrantedAuthority</tt>s. </sec:authorize>
一個常見的要求是隻顯示一個特定的連接,若是用戶實際上被容許點擊它。 咱們如何才能事先決定是否容許某些事情? 該標籤也能夠以替代模式進行操做,能夠將特定的URL定義爲屬性。 若是用戶被容許調用該URL,那麼標籤主體將被評估,不然將被跳過。 因此你可能會有這樣的東西
<sec:authorize url="/admin"> This content will only be visible to users who are authorized to send requests to the "/admin" URL. </sec:authorize>
要使用此標籤,還必須在應用程序上下文中具備WebInvocationPrivilegeEvaluator的實例。若是您使用命名空間,則會自動註冊。這是DefaultWebInvocationPrivilegeEvaluator的一個實例,它爲提供的URL建立一個虛擬Web請求,並調用安全攔截器來查看請求是成功仍是失敗。這容許您委託您在<http>命名空間配置中使用intercept-url聲明定義的訪問控制設置,並保存必須在JSP中複製信息(例如必需的角色)。此方法也能夠與方法屬性組合,提供HTTP方法,以進行更具體的匹配。
經過將var屬性設置爲變量名稱,能夠將頁面上下文範圍變量存儲在頁面上下文範圍變量中,從而避免複製和從新評估條件頁。
4.2.1。禁用標籤受權進行測試
在未經受權的用戶隱藏頁面中的連接不會阻止他們訪問該URL。例如,他們能夠直接將其輸入到瀏覽器中。做爲測試過程的一部分,您可能但願透露隱藏的區域,以便檢查連接是否真的在後端被保護。若是將系統屬性spring.security.disableUISecurity設置爲true,則受權標籤仍將運行,但不會隱藏其內容。默認狀況下,它還將包含<span class =「securityHiddenUI」> ... </ span>標籤的內容。這容許您顯示具備特定CSS樣式(例如不一樣背景顏色)的「隱藏」內容。例如,嘗試運行啓用此屬性的「tutorial」示例應用程序。
您還能夠設置屬性spring.security.securedUIPrefix和spring.security.securedUISuffix,若是要從默認的span標籤更改周圍的文本(或使用空字符串將其徹底刪除)。
4.3。authentication標籤
此標籤容許訪問存儲在安全上下文中的當前Authentication對象。它直接在JSP中呈現對象的屬性。因此,例如,若是Authentication的principal屬性是Spring Security的UserDetails對象的一個實例,那麼使用<sec:authentication property =「principal.username」/>將渲染當前用戶的名稱。
固然,沒有必要對這種事情使用JSP標籤,有些人喜歡在視圖中保持儘量少的邏輯。您能夠訪問MVC控制器中的Authentication對象(經過調用SecurityContextHolder.getContext()。getAuthentication()),並將數據直接添加到模型中,以便經過視圖呈現。
4.4。accesscontrollist標籤
此標記僅在與Spring Security的ACL模塊一塊兒使用時有效。它檢查指定域對象所需權限的逗號分隔列表。若是當前用戶具備任何這些權限,那麼標籤主體將被評估。若是沒有,將被跳過。一個例子多是
<sec:accesscontrollist hasPermission="1,2" domainObject="${someObject}"> This will be shown if the user has either of the permissions represented by the values "1" or "2" on the given object. </sec:accesscontrollist>
權限被傳遞給應用程序上下文中定義的PermissionFactory,將它們轉換爲ACL權限實例,所以它們能夠是工廠支持的任何格式 - 它們不必定是整數,它們能夠是像READ或WRITE 。若是未找到PermissionFactory,將使用DefaultPermissionFactory的實例。來自應用程序上下文的AclService將用於加載提供的對象的Acl實例。 Acl將被調用所需的權限,以檢查是否授予其中任何一個。
該標籤也支持var屬性,與authorize標籤相同。
4.5。 csrfInput標籤
若是啓用了CSRF保護,則此標記將爲CSRF保護令牌插入具備正確名稱和值的隱藏表單域。若是未啓用CSRF保護,則此標籤不會輸出任何內容。
一般,Spring Security會爲您使用的任何<form:form>標籤自動插入一個CSRF表單域,可是若是因爲某些緣由您不能使用<form:form>,則csrfInput是一個方便的替換。
您應該將此標籤放在HTML <form> </ form>塊中,一般會放置其餘輸入字段。不要把這個標籤放在一個Spring <form:form> </ form:form> block中 - Spring Security會自動處理Spring表單。
<form method="post" action="/do/something"> <sec:csrfInput /> Name:<br /> <input type="text" name="name" /> ... </form>
4.6。 csrfMetaTags標籤
若是啓用了CSRF保護,則此標記插入包含CSRF保護令牌表單字段和頭名稱以及CSRF保護令牌值的元標記。 這些元標記在您的應用程序中在JavaScript中使用CSRF保護是有用的。
您應該將csrfMetaTags放在HTML <head> </ head>塊中,一般會放置其餘元標記。 使用此標籤後,您可使用JavaScript輕鬆訪問表單字段名稱,標題名稱和令牌值。 在這個例子中使用JQuery來使任務更容易。
<!DOCTYPE html> <html> <head> <title>CSRF Protected JavaScript Page</title> <meta name="description" content="This is the description for this page" /> <sec:csrfMetaTags /> <script type="text/javascript" language="javascript"> var csrfParameter = $("meta[name='_csrf_parameter']").attr("content"); var csrfHeader = $("meta[name='_csrf_header']").attr("content"); var csrfToken = $("meta[name='_csrf']").attr("content"); // using XMLHttpRequest directly to send an x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "http://www.example.org/do/something", true); ajax.setRequestHeader("Content-Type", "application/x-www-form-urlencoded data"); ajax.send(csrfParameter + "=" + csrfToken + "&name=John&..."); // using XMLHttpRequest directly to send a non-x-www-form-urlencoded request var ajax = new XMLHttpRequest(); ajax.open("POST", "http://www.example.org/do/something", true); ajax.setRequestHeader(csrfHeader, csrfToken); ajax.send("..."); // using JQuery to send an x-www-form-urlencoded request var data = {}; data[csrfParameter] = csrfToken; data["name"] = "John"; ... $.ajax({ url: "http://www.example.org/do/something", type: "POST", data: data, ... }); // using JQuery to send a non-x-www-form-urlencoded request var headers = {}; headers[csrfHeader] = csrfToken; $.ajax({ url: "http://www.example.org/do/something", type: "POST", headers: headers, ... }); <script> </head> <body> ... </body> </html>
若是沒有啓用CSRF保護,csrfMetaTags不會輸出任何內容。
5. Java認證和受權服務(JAAS)提供商
5.1。概觀
Spring Security提供了一個可以將認證請求委託給Java認證和受權服務(JAAS)的軟件包。此包將在下面詳細討論。
5.2。 AbstractJaasAuthenticationProvider
AbstractJaasAuthenticationProvider是提供的JAAS AuthenticationProvider實現的基礎。子類必須實現一個建立LoginContext的方法。 AbstractJaasAuthenticationProvider具備能夠注入到其中的若干依賴關係,這些依賴關係將在下面討論。
5.2.1。 JAAS CallbackHandler
大多數JAAS LoginModule須要某種回調。這些回調一般用於從用戶處獲取用戶名和密碼。
在Spring Security部署中,Spring Security負責此用戶交互(經過身份驗證機制)。所以,當認證請求被委派給JAAS時,Spring Security的認證機制將已經徹底填充了包含JAAS LoginModule所需的全部信息的Authentication對象。
所以,Spring Security的JAAS包提供了兩個默認的回調處理程序JaasNameCallbackHandler和JaasPasswordCallbackHandler。這些回調處理程序中的每個都實現JaasAuthenticationCallbackHandler。在大多數狀況下,這些回調處理程序能夠在不瞭解內部機制的狀況下簡單地使用。
對於那些須要徹底控制回調行爲的人,內部AbstractJaasAuthenticationProvider將這些JaasAuthenticationCallbackHandler與一個InternalCallbackHandler進行包裝。 InternalCallbackHandler是實際實現JAAS正常CallbackHandler接口的類。任什麼時候候使用JAAS LoginModule,它都會傳遞一個應用程序上下文配置的內部列表。若是LoginModule針對InternalCallbackHandler請求回調,則回調將被傳遞給正在包裝的JaasAuthenticationCallbackHandler。
5.2.2。 JAAS AuthorityGranter
JAAS與principals合做。即便「角色」也被表示爲JAAS中的主體。另外一方面,Spring Security與Authentication對象一塊兒使用。每一個驗證對象包含一個主體和多個GrantedAuthority。爲了方便這些不一樣概念之間的映射,Spring Security的JAAS包包括一個AuthorityGranter接口。
一個AuthorityGranter負責檢查JAAS主體並返回一組String,表明分配給委託人的權限。對於每一個返回的權限字符串,AbstractJaasAuthenticationProvider建立一個JaasGrantedAuthority(它實現Spring Security的GrantedAuthority接口),該接口包含權限字符串和AuthorityGranter傳遞的JAAS主體。 AbstractJaasAuthenticationProvider經過首先使用JAAS LoginModule成功驗證用戶的憑據,而後訪問返回的LoginContext,來獲取JAAS主體。調用LoginContext.getSubject()。getPrincipals(),每一個生成的主體都傳遞給根據AbstractJaasAuthenticationProvider.setAuthorityGranters(List)屬性定義的每一個AuthorityGranter。
給予每一個JAAS主體具備實現特定含義的Spring Security不包括任何生產AuthorityGranter。可是,在單元測試中有一個TestAuthorityGranter,它演示了一個簡單的AuthorityGranter實現。
5.3。 DefaultJaasAuthenticationProvider
DefaultJaasAuthenticationProvider容許將JAAS配置對象注入它做爲依賴。而後使用注入的JAAS配置建立一個LoginContext。這意味着DefaultJaasAuthenticationProvider不會像JaasAuthenticationProvider那樣綁定任何特定的配置實現。
5.3.1。 InMemoryConfiguration
爲了方便將配置注入DefaultJaasAuthenticationProvider,提供了名爲InMemoryConfiguration的內存實現中的默認值。實現構造函數接受一個Map,其中每一個鍵表示一個登陸配置名稱,該值表示一個AppConfigurationEntry的數組。 InMemoryConfiguration還支持一個AppConfigurationEntry對象的默認數組,若是在所提供的Map中找不到映射,則將使用它們。有關詳細信息,請參閱InMemoryConfiguration的類級別javadoc。
5.3.2。 DefaultJaasAuthenticationProvider示例配置
雖然InMemoryConfiguration的Spring配置能夠比標準的JAAS配置文件更冗長,可是與DefaultJaasAuthenticationProvider結合使用它比JaasAuthenticationProvider更靈活,由於它不依賴於默認的配置實現。
下面提供了使用InMemoryConfiguration的DefaultJaasAuthenticationProvider的示例配置。 請注意,配置的自定義實現也能夠輕鬆地注入到DefaultJaasAuthenticationProvider中。
<bean id="jaasAuthProvider" class="org.springframework.security.authentication.jaas.DefaultJaasAuthenticationProvider"> <property name="configuration"> <bean class="org.springframework.security.authentication.jaas.memory.InMemoryConfiguration"> <constructor-arg> <map> <!-- SPRINGSECURITY is the default loginContextName for AbstractJaasAuthenticationProvider --> <entry key="SPRINGSECURITY"> <array> <bean class="javax.security.auth.login.AppConfigurationEntry"> <constructor-arg value="sample.SampleLoginModule" /> <constructor-arg> <util:constant static-field= "javax.security.auth.login.AppConfigurationEntry$LoginModuleControlFlag.REQUIRED"/> </constructor-arg> <constructor-arg> <map></map> </constructor-arg> </bean> </array> </entry> </map> </constructor-arg> </bean> </property> <property name="authorityGranters"> <list> <!-- You will need to write your own implementation of AuthorityGranter --> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
5.4。JaasAuthenticationProvider
JaasAuthenticationProvider假定默認配置是ConfigFile的一個實例。 這是爲了嘗試更新配置而進行的。 JaasAuthenticationProvider而後使用默認配置來建立LoginContext。
假設咱們有一個JAAS登陸配置文件/WEB-INF/login.conf,其中包含如下內容:
JAASTest { sample.SampleLoginModule required; };
像全部Spring Security bean同樣,JaasAuthenticationProvider經過應用程序上下文進行配置。 如下定義將對應於上述JAAS登陸配置文件:
<bean id="jaasAuthenticationProvider" class="org.springframework.security.authentication.jaas.JaasAuthenticationProvider"> <property name="loginConfig" value="/WEB-INF/login.conf"/> <property name="loginContextName" value="JAASTest"/> <property name="callbackHandlers"> <list> <bean class="org.springframework.security.authentication.jaas.JaasNameCallbackHandler"/> <bean class="org.springframework.security.authentication.jaas.JaasPasswordCallbackHandler"/> </list> </property> <property name="authorityGranters"> <list> <bean class="org.springframework.security.authentication.jaas.TestAuthorityGranter"/> </list> </property> </bean>
5.5。做爲主題運行
若是配置,JaasApiIntegrationFilter將嘗試做爲JaasAuthenticationToken上的Subject運行。這意味着可使用如下方式訪問主題:
Subject subject = Subject.getSubject(AccessController.getContext());
可使用jaas-api-provision屬性輕鬆配置此集成。當與依賴於JAAS主題的舊式或外部API進行集成時,此功能很是有用。
6. CAS認證
6.1。概觀
JA-SIG生產一種名爲CAS的企業級單點登陸系統。與其餘舉措不一樣,JA-SIG的中央認證服務是開源,普遍使用,易於理解,獨立於平臺,並支持代理功能。 Spring Security徹底支持CAS,並提供從Spring Security的單應用程序部署到企業級CAS服務器保護的多應用程序部署的簡單遷移路徑。
您能夠在http://www.ja-sig.org/cas上了解更多關於CAS的信息。您還須要訪問此站點才能下載CAS Server文件。
6.2。 CAS如何工做
雖然CAS網站包含詳細介紹CAS體系結構的文檔,但咱們在Spring Security的上下文中再次介紹通常性概述。 Spring Security 3.x支持CAS 3.在編寫本文時,CAS服務器爲3.4版本。
在企業的某個地方,您將須要設置一個CAS服務器。 CAS服務器只是一個標準的WAR文件,因此設置服務器並不困難。在WAR文件中,您將自定義用戶顯示的登陸名和其餘單一登陸頁面。
部署CAS 3.4服務器時,還須要在CAS中包含的deployerConfigContext.xml中指定一個AuthenticationHandler。 AuthenticationHandler有一個簡單的方法,它返回一個關於一組給定的憑據是否有效的布爾值。您的AuthenticationHandler實現將須要連接到某種類型的後端認證存儲庫,例如LDAP服務器或數據庫。 CAS自己包括許多AuthenticationHandler的開箱即用的協助。當您下載並部署服務器戰爭文件時,設置爲成功驗證輸入與用戶名匹配的密碼的用戶,這對於測試很是有用。
除了CAS服務器自己,其餘關鍵的用戶固然是部署在整個企業中的安全Web應用程序。這些Web應用程序被稱爲「服務」。有三種類型的服務。那些認證服務票,那些能夠得到代理票的人,以及認證代理票的那些。驗證代理機票是不一樣的,由於代理列表必須通過驗證,一般能夠重用代理機票。
6.2.1。 Spring Security和CAS交互序列
Web瀏覽器,CAS服務器和Spring Security安全服務之間的基本交互以下:
- 網絡用戶正在瀏覽服務的公共頁面。 CAS或Spring Security不涉及。
- 用戶最終請求一個安全的頁面,或者它使用的一個bean是安全的。 Spring Security的ExceptionTranslationFilter將檢測到AccessDeniedException或AuthenticationException。
- 由於用戶的Authentication對象(或缺乏)致使了AuthenticationException,因此ExceptionTranslationFilter會調用配置的AuthenticationEntryPoint。若是使用CAS,這將是CasAuthenticationEntryPoint類。
- CasAuthenticationEntryPoint將用戶的瀏覽器重定向到CAS服務器。它還將指示一個服務參數,它是Spring Security服務(您的應用程序)的回調URL。例如,瀏覽器重定向到的URL多是https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check。
- 用戶瀏覽器重定向到CAS後,系統將提示輸入用戶名和密碼。若是用戶顯示一個表示它們之前登陸的會話cookie,則不會再提示他們再次登陸(這個過程有一個例外,稍後將介紹)。 CAS將使用上面討論的PasswordHandler(或AuthenticationHandler,若是使用CAS 3.0)來決定用戶名和密碼是否有效。
- 成功登陸後,CAS將把用戶的瀏覽器重定向到原始服務。它還將包括一個票據參數,它是一個不透明的字符串,表明「服務票證」。繼續咱們早期的示例,瀏覽器重定向到的URL多是https://server3.company.com/webapp/j_spring_cas_security_check?ticket=ST-0-ER94xMJmn6pha35CQRoZ。
- 回到服務Web應用程序,CasAuthenticationFilter老是監聽/ j_spring_cas_security_check的請求(這是可配置的,但咱們將使用本介紹中的默認值)。處理過濾器將構造一個表示服務票證的UsernamePasswordAuthenticationToken。主體將等於CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER,而憑證將是服務票據不透明值。而後,該認證請求將交給配置的AuthenticationManager。
- AuthenticationManager實現將是ProviderManager,它依次配置了CasAuthenticationProvider。 CasAuthenticationProvider僅響應包含CAS特定主體(如CasAuthenticationFilter.CAS_STATEFUL_IDENTIFIER)和CasAuthenticationToken(稍後討論)的UsernamePasswordAuthenticationToken。
- CasAuthenticationProvider將使用TicketValidator實現驗證服務票證。這一般是一個Cas20ServiceTicketValidator,它是CAS客戶端庫中包含的一個類。若是應用程序須要驗證代理機票,則使用Cas20ProxyTicketValidator。 TicketValidator向CAS服務器發出HTTPS請求,以驗證服務票證。它還可能包括代理回調URL,該URL包含在此示例中:https://my.company.com/cas/proxyValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Fj_spring_cas_security_check&ticket=ST- 0-ER94xMJmn6pha35CQRoZ&pgtUrl = HTTPS://server3.company.com/webapp/j_spring_cas_security_proxyreceptor。
- 返回CAS服務器,驗證請求將被接收。若是所提供的服務票證與該票據發出的服務URL匹配,則CAS將提供XML中的確定回覆,表示用戶名。若是任何代理涉及身份驗證(以下所述),代理列表也包含在XML響應中。
- [可選]若是對CAS驗證服務的請求包含代理回調URL(在pgtUrl參數中),CAS將在XML響應中包含一個pgtIou字符串。這個pgtIou表明一個代理受權票IOU。而後CAS服務器將建立本身的HTTPS鏈接回到pgtUrl。這是爲了相互認證CAS服務器和聲明的服務URL。 HTTPS鏈接將用於向原始Web應用程序發送代理受權單。例如,https://server3.company.com/webapp/j_spring_cas_security_proxyreceptor?pgtIou=PGTIOU-0-R0zlgrl4pdAQwBvJWO3vnNpevwqStbSGcq3vKB2SqSFFRnjPHt&pgtId=PGT-1-si9YkkHLrtACBo64rmsi3v2nf7cpCResXg5MpESZFArbaZiOKH。
- Cas20TicketValidator將解析從CAS服務器收到的XML。它將返回到CasAuthenticationProvider一個TicketResponse,其中包括用戶名(強制性),代理列表(若是涉及到)和代理受權票據IOU(若是請求代理回調)。
下一個CasAuthenticationProvider將調用一個配置的CasProxyDecider。 CasProxyDecider指示是否在票證中的代理列表
6.3。 CAS客戶端的配置
因爲Spring Security,CAS的Web應用程序很容易。假設你已經知道使用Spring Security的基礎知識,因此這些再也不在下面被覆蓋。咱們假設正在使用基於命名空間的配置,並根據須要添加到CAS bean中。每一個部分創建在上一節。一個fullCAS示例應用程序能夠在Spring Security Samples中找到。
6.3.1。服務票證實
本節介紹如何設置Spring Security以驗證服務門票。一般這是一個Web應用程序須要的。您將須要在應用程序上下文中添加一個ServiceProperties bean。這表明您的CAS服務:
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> <property name="service" value="https://localhost:8443/cas-sample/j_spring_cas_security_check"/> <property name="sendRenew" value="false"/> </bean>
該服務必須等於由CasAuthenticationFilter監視的URL。 sendRenew默認爲false,但若是應用程序特別敏感,則應將其設置爲true。這個參數是告訴CAS登陸服務登陸的一個登陸名是不可接受的。相反,用戶須要從新輸入用戶名和密碼才能訪問該服務。
如下bean應配置爲啓動CAS身份驗證過程(假設您正在使用命名空間配置):
<security:http entry-point-ref="casEntryPoint"> ... <security:custom-filter position="CAS_FILTER" ref="casFilter" /> </security:http> <bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> <property name="authenticationManager" ref="authenticationManager"/> </bean> <bean id="casEntryPoint" class="org.springframework.security.cas.web.CasAuthenticationEntryPoint"> <property name="loginUrl" value="https://localhost:9443/cas/login"/> <property name="serviceProperties" ref="serviceProperties"/> </bean>
要使CAS運行,ExceptionTranslationFilter必須將其authenticationEntryPoint屬性設置爲CasAuthenticationEntryPoint bean。這可使用入門點參考來作到這一點。 CasAuthenticationEntryPoint必須引用ServiceProperties bean(上面討論過),它提供了企業的CAS登陸服務器的URL。這是用戶瀏覽器將被重定向的位置。
CasAuthenticationFilter與UsernamePasswordAuthenticationFilter具備很是類似的屬性(用於基於表單的登陸)。您可使用這些屬性來定製身份驗證成功和失敗的行爲。
接下來,您須要添加一個CasAuthenticationProvider及其協做者:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref="casAuthenticationProvider" /> </security:authentication-manager> <bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> <property name="authenticationUserDetailsService"> <bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper"> <constructor-arg ref="userService" /> </bean> </property> <property name="serviceProperties" ref="serviceProperties" /> <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator"> <constructor-arg index="0" value="https://localhost:9443/cas" /> </bean> </property> <property name="key" value="an_id_for_this_auth_provider_only"/> </bean> <security:user-service id="userService"> <security:user name="joe" password="joe" authorities="ROLE_USER" /> ... </security:user-service>
一旦CASAuthenticationProvider經過CAS認證,使用UserDetailsService實例來加載用戶的權限。咱們在這裏展現了一個簡單的內存設置。請注意,CasAuthenticationProvider實際上並無使用密碼進行身份驗證,但它確實使用了權限。
若是您回到「CAS工做部分」部分,那麼這些bean都是合理的。
這完成了CAS的最基本的配置。若是您沒有發生任何錯誤,您的Web應用程序應該在CAS單點登陸的框架內快樂工做。 Spring Security的其餘任何部分都不須要關心CAS處理認證的事實。在如下部分中,咱們將討論一些(可選)更高級的配置。
6.3.2。 單一註銷
CAS協議支持單一註銷,能夠輕鬆添加到您的Spring Security配置中。 如下是處理單一註銷的Spring Security配置的更新
<security:http entry-point-ref="casEntryPoint"> ... <security:logout logout-success-url="/cas-logout.jsp"/> <security:custom-filter ref="requestSingleLogoutFilter" before="LOGOUT_FILTER"/> <security:custom-filter ref="singleLogoutFilter" before="CAS_FILTER"/> </security:http> <!-- This filter handles a Single Logout Request from the CAS Server --> <bean id="singleLogoutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter"/> <!-- This filter redirects to the CAS Server to signal Single Logout should be performed --> <bean id="requestSingleLogoutFilter" class="org.springframework.security.web.authentication.logout.LogoutFilter"> <constructor-arg value="https://localhost:9443/cas/logout"/> <constructor-arg> <bean class= "org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"/> </constructor-arg> <property name="filterProcessesUrl" value="/j_spring_cas_security_logout"/> </bean>
註銷元素將用戶記錄在本地應用程序以外,但不會終止與CAS服務器或已登陸的任何其餘應用程序的會話。 requestSingleLogoutFilter過濾器將容許/ spring_security_cas_logout的url重定向到配置的CAS Server註銷URL。那麼CAS服務器將向登陸的全部服務發送單一註銷請求。 singleLogoutFilter經過在靜態Map中查找HttpSession而後使其無效來處理Single Logout請求。
這可能會混淆爲何須要註銷元素和singleLogoutFilter。因爲SingleSignOutFilter將HttpSession存儲在靜態Map中以便調用invalidate,因此被認爲是最佳實踐。經過上述配置,註銷流程爲:
- 用戶請求/ j_spring_security_logout,將用戶登陸本地應用程序,並將用戶發送到註銷成功頁面。
- 註銷成功頁面/cas-logout.jsp應指示用戶單擊指向/ j_spring_cas_security_logout的連接,以便退出全部應用程序。
- 當用戶單擊連接時,用戶被重定向到CAS單個註銷URL(https:// localhost:9443 / cas / logout)。
- 在CAS服務器端,CAS單一註銷URL而後將一個註銷請求提交給全部的CAS服務。在CAS服務端,JASIG的SingleSignOutFilter經過使原始會話無效來處理註銷請求。
下一步是將如下內容添加到您的web.xml中
<filter> <filter-name>characterEncodingFilter</filter-name> <filter-class> org.springframework.web.filter.CharacterEncodingFilter </filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <listener> <listener-class> org.jasig.cas.client.session.SingleSignOutHttpSessionListener </listener-class> </listener>
當使用SingleSignOutFilter時,您可能會遇到一些編碼問題。所以,建議在使用SingleSignOutFilter時添加CharacterEncodingFilter以確保字符編碼正確。再次參考JASIG的文檔資料。 SingleSignOutHttpSessionListener確保當HttpSession到期時,用於單次註銷的映射被刪除。
6.3.3。使用CAS驗證無狀態服務
本節介紹如何使用CAS對服務進行身份驗證。換句話說,本節討論如何設置使用與CAS進行身份驗證的服務的客戶端。下一節介紹如何使用CAS進行身份驗證設置無狀態服務。
配置CAS以獲取代理受權單
爲了對無狀態服務進行身份驗證,應用程序須要得到代理受權票證(PGT)。本節介紹如何配置Spring Security,以便在thencas-st [服務票證驗證]配置中獲取PGT構建。
第一步是在您的Spring Security配置中包含一個ProxyGrantingTicketStorage。這用於存儲由CasAuthenticationFilter獲取的PGT,以便它們可用於獲取代理票證。示例配置以下所示
<!-- NOTE: In a real application you should not use an in memory implementation. You will also want to ensure to clean up expired tickets by calling ProxyGrantingTicketStorage.cleanup() --> <bean id="pgtStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl"/>
下一步是更新CasAuthenticationProvider以得到代理機票。爲此,請用Cas20ProxyTicketValidator替換Cas20ServiceTicketValidator。 proxyCallbackUrl應設置爲應用程序將接收PGT的URL。最後,配置還應該引用ProxyGrantingTicketStorage,所以可使用PGT來獲取代理機票。您能夠在下面找到配置更改的示例。
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="proxyCallbackUrl" value="https://localhost:8443/cas-sample/j_spring_cas_security_proxyreceptor"/> <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> </bean> </property> </bean>
最後一步是更新CasAuthenticationFilter以接受PGT並將其存儲在ProxyGrantingTicketStorage中。 proxyReceptorUrl與Cas20ProxyTicketValidator的proxyCallbackUrl匹配很重要。示例配置以下所示。
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="proxyGrantingTicketStorage" ref="pgtStorage"/> <property name="proxyReceptorUrl" value="/j_spring_cas_security_proxyreceptor"/> </bean>
使用代理機構調用無狀態服務
如今Spring Security得到PGT,您可使用它們來建立可用於向無狀態服務進行身份驗證的代理憑證。 CAS示例應用程序在ProxyTicketSampleServlet中包含一個工做示例。示例代碼以下:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // NOTE: The CasAuthenticationToken can also be obtained using // SecurityContextHolder.getContext().getAuthentication() final CasAuthenticationToken token = (CasAuthenticationToken) request.getUserPrincipal(); // proxyTicket could be reused to make calls to the CAS service even if the // target url differs final String proxyTicket = token.getAssertion().getPrincipal().getProxyTicketFor(targetUrl); // Make a remote call using the proxy ticket final String serviceUrl = targetUrl+"?ticket="+URLEncoder.encode(proxyTicket, "UTF-8"); String proxyResponse = CommonUtils.getResponseFromServer(serviceUrl, "UTF-8"); ... }
6.3.4。代理機票認證
CasAuthenticationProvider區分有狀態和無狀態的客戶端。有狀態的客戶端被認爲是提交給CasAuthenticationFilter的filterProcessUrl的任何客戶端。無狀態客戶端是在FilterProcessUrl之外的URL上向CasAuthenticationFilter提供認證請求的任何客戶端。
由於遠程處理協議沒有辦法在HttpSession的上下文中呈現本身,因此不可能依賴於在請求之間的會話中存儲安全上下文的默認作法。此外,因爲CAS服務器在TicketValidator驗證以後使票據無效,所以在後續請求中呈現相同的代理票證將不起做用。
一個明顯的選擇是不使用CAS來遠程處理協議客戶端。然而,這將消除CAS的許多理想特徵。做爲中間層,CasAuthenticationProvider使用StatelessTicketCache。這僅用於使用等於CasAuthenticationFilter.CAS_STATELESS_IDENTIFIER的主體的無狀態客戶端。 CasAuthenticationProvider會將生成的CasAuthenticationToken存儲在StatelessTicketCache中,並鍵入代理機票。所以,遠程協議客戶端能夠呈現相同的代理憑證,而且CasAuthenticationProvider將不須要與CAS服務器聯繫以進行驗證(除了第一個請求)。一旦通過身份驗證,代理機票能夠用於除原始目標服務以外的其餘網址。
本部分創建在前面的部分以適應代理票證驗證。第一步是指定驗證全部工件,以下所示。
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties"> ... <property name="authenticateAllArtifacts" value="true"/> </bean>
下一步是爲CasAuthenticationFilter指定serviceProperties和authenticationDetailsSource。 serviceProperties屬性指示CasAuthenticationFilter嘗試對全部工件進行身份驗證,而不是僅對存在於filterProcessUrl上的工件進行身份驗證。 ServiceAuthenticationDetailsSource建立一個ServiceAuthenticationDetails,確保當驗證門票時,基於HttpServletRequest的當前URL被用做服務URL。生成服務URL的方法能夠經過註冊自定義的AuthenticationDetailsSource來自定義,該代碼返回一個自定義的ServiceAuthenticationDetails。
<bean id="casFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter"> ... <property name="serviceProperties" ref="serviceProperties"/> <property name="authenticationDetailsSource"> <bean class= "org.springframework.security.cas.web.authentication.ServiceAuthenticationDetailsSource"> <constructor-arg ref="serviceProperties"/> </bean> </property> </bean>
您還須要更新CasAuthenticationProvider來處理代理機票。 爲此,請用Cas20ProxyTicketValidator替換Cas20ServiceTicketValidator。 您將須要配置statelessTicketCache以及您要接受的代理。 您能夠在下面找到接受全部代理所需更新的示例。
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider"> ... <property name="ticketValidator"> <bean class="org.jasig.cas.client.validation.Cas20ProxyTicketValidator"> <constructor-arg value="https://localhost:9443/cas"/> <property name="acceptAnyProxy" value="true"/> </bean> </property> <property name="statelessTicketCache"> <bean class="org.springframework.security.cas.authentication.EhCacheBasedTicketCache"> <property name="cache"> <bean class="net.sf.ehcache.Cache" init-method="initialise" destroy-method="dispose"> <constructor-arg value="casTickets"/> <constructor-arg value="50"/> <constructor-arg value="true"/> <constructor-arg value="false"/> <constructor-arg value="3600"/> <constructor-arg value="900"/> </bean> </property> </bean> </property> </bean>
7. X.509認證
7.1。概觀
X.509證書身份驗證最多見的用途是在使用SSL時驗證服務器的身份,最多見的是在瀏覽器中使用HTTPS。瀏覽器將自動檢查由服務器呈現的證書是否已被其維護的可靠證書頒發機構的列表之一發出(即數字簽名)。
您也可使用SSL進行「相互認證」;服務器而後將從SSL客戶端請求有效的證書做爲SSL握手的一部分。服務器將經過檢查其證書由可接受的權限簽名來驗證客戶端。若是提供了有效的證書,則能夠經過應用程序中的servlet API獲取。 Spring Security X.509模塊使用過濾器提取證書。它將證書映射到應用程序用戶,並加載該用戶的一組已受權的權限,以便與標準的Spring Security基礎結構一塊兒使用。
在嘗試使用Spring Security以前,您應該熟悉使用證書併爲您的servlet容器設置客戶端身份驗證。大部分工做是在建立和安裝合適的證書和密鑰。例如,若是您使用Tomcat,請閱讀如下指示http://tomcat.apache.org/tomcat-6.0-doc/ssl-howto.html。在使用Spring Security進行嘗試以前,請務必從新開始工做
7.2。將X.509認證添加到Web應用程序
啓用X.509客戶端身份驗證很是簡單。只需將<x509 />元素添加到您的http安全命名空間配置。
<http> ... <x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>; </http>
該元素有兩個可選屬性:
- 主題主要正則表達式。用於從證書主題名稱中提取用戶名的正則表達式。默認值如上所示。這是將被傳遞給UserDetailsService的用戶名,以加載用戶的權限。
- 用戶服務-REF。這是與X.509一塊兒使用的UserDetailsService的bean ID。若是您的應用程序上下文中只定義了一個,則不須要它。
主題 - 正則表達式應包含單個組。例如,默認表達式「CN =(。*?)」匹配通用名稱字段。所以,若是證書中的主題名稱爲「CN = Jimi Hendrix,OU = ...」,則將給出「Jimi Hendrix」用戶名。比賽不區分大小寫。因此「emailAddress =(。?)」將匹配「EMAILADDRESS = jimi @ hendrix.org,CN = ...」給用戶名「jimi@hendrix.org」。若是客戶端呈現證書而且成功提取了有效的用戶名,那麼在安全上下文中應該有一個有效的Authentication對象。若是沒有找到證書,或者沒有找到相應的用戶,則安全上下文將保持爲空。這意味着您能夠輕鬆地使用X.509身份驗證和其餘選項(如基於表單的登陸)。
7.3。在Tomcat中設置SSL
Spring Security項目中的samples / certificate目錄中有一些預生成的證書。若是不想本身生成,可使用這些來啓用SSL進行測試。文件server.jks包含服務器證書,私鑰和頒發證書頒發機構證書。還有一些來自示例應用程序的用戶的客戶端證書文件。您能夠在瀏覽器中安裝這些,以啓用SSL客戶端身份驗證。
要運行具備SSL支持的tomcat,請將server.jks文件放入tomcat conf目錄,並將如下鏈接器添加到server.xml文件
<Connector port="8443" protocol="HTTP/1.1" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS" keystoreFile="${catalina.home}/conf/server.jks" keystoreType="JKS" keystorePass="password" truststoreFile="${catalina.home}/conf/server.jks" truststoreType="JKS" truststorePass="password" />
若是仍然但願SSL鏈接成功,即便客戶端不提供證書,clientAuth也能夠設置爲須要。不提供證書的客戶端將沒法訪問Spring Security所保護的任何對象,除非您使用非X.509認證機制,例如表單驗證。
8.運行認證替換
8.1。概觀
AbstractSecurityInterceptor可以在安全對象回調階段臨時替換SecurityContext和SecurityContextHolder中的Authentication對象。只有當AuthenticationManager和AccessDecisionManager成功處理了原始的Authentication對象時,纔會發生這種狀況。 RunAsManager將指示SecurityInterceptorCallback中應該使用的替換Authentication對象(若是有)。
經過在安全對象回調階段臨時替換Authentication對象,安全調用將可以調用須要不一樣身份驗證和受權憑證的其餘對象。它還能夠對特定的GrantedAuthority對象執行任何內部安全檢查。因爲Spring Security提供了一些輔助類,能夠根據SecurityContextHolder的內容自動配置遠程處理協議,這些運行替換在調用遠程Web服務時特別有用
8.2。組態
RunAsManager接口由Spring Security提供:
Authentication buildRunAs(Authentication authentication, Object object, List<ConfigAttribute> config); boolean supports(ConfigAttribute attribute); boolean supports(Class clazz);
第一種方法返回在方法調用期間應替換現有Authentication對象的Authentication對象。若是該方法返回null,則表示不該進行替換。 AbstractSecurityInterceptor使用第二種方法做爲其啓動驗證配置屬性的一部分。支持(Class)方法由安全攔截器實現調用,以確保配置的RunAsManager支持安全攔截器將顯示的安全對象的類型。
Spring Security提供了一個RunAsManager的具體實現。若是任何ConfigAttribute以RUN_AS_開頭,RunAsManagerImpl類將返回一個替換RunAsUserToken。若是找到任何這樣的ConfigAttribute,則替換的RunAsUserToken將包含與原始Authentication對象相同的主體,憑據和授予的權限,以及每一個RUN_AS_ ConfigAttribute的新GrantedAuthorityImpl。每一個新的GrantedAuthorityImpl將以ROLE_爲前綴,後跟RUN_AS ConfigAttribute。例如,RUN_AS_SERVER將致使替換RunAsUserToken包含ROLE_RUN_AS_SERVER授予權限。
替換RunAsUserToken就像任何其餘的Authentication對象同樣。它須要經過AuthenticationManager進行身份驗證,可能經過委託給合適的AuthenticationProvider進行身份驗證。 RunAsImplAuthenticationProvider執行此類身份驗證。它只會接受呈現的任何RunAsUserToken的有效。
爲了確保惡意代碼不會建立RunAsUserToken並提供它,以保證RunAsImplAuthenticationProvider的接受,密鑰的哈希存儲在全部生成的令牌中。 RunAsManagerImpl和RunAsImplAuthenticationProvider在具備相同鍵的bean上下文中建立:
<bean id="runAsManager" class="org.springframework.security.access.intercept.RunAsManagerImpl"> <property name="key" value="my_run_as_password"/> </bean> <bean id="runAsAuthenticationProvider" class="org.springframework.security.access.intercept.RunAsImplAuthenticationProvider"> <property name="key" value="my_run_as_password"/> </bean>
經過使用相同的密鑰,每一個RunAsUserToken均可以經過已批准的RunAsManagerImpl來建立。出於安全緣由,RunAsUserToken在建立後是不可變的
9. Spring Security加密模塊
9.1。介紹
Spring Security Crypto模塊支持對稱加密,密鑰生成和密碼編碼。該代碼做爲核心模塊的一部分進行分發,但不依賴任何其餘Spring Security(或Spring)代碼。
9.2。加密機
Encryptors類提供了構建對稱加密器的工廠方法。使用此類,您能夠建立ByteEncryptors以原始byte []形式加密數據。您還能夠構建TextEncryptors來加密文本字符串。加密程序是線程安全的。
9.2.1。 BytesEncryptor
使用Encryptors.standard工廠方法構建一個「standard」BytesEncryptor:
Encryptors.standard("password", "salt");
「標準」加密方法是使用PKCS#5的PBKDF2(基於密碼的密鑰導出功能#2)的256位AES。該方法須要Java 6.用於生成SecretKey的密碼應該保存在一個安全的地方,而不是共享的。若是您的加密數據受到威脅,該鹽將用於防止對該密鑰的字典攻擊。也應用16字節的隨機初始化向量,所以每一個加密的消息是惟一的。
提供的鹽應該是十六進制編碼的String形式,是隨機的,長度至少爲8個字節。這樣的鹽可使用KeyGenerator生成:
String salt = KeyGenerators.string().generateKey(); // generates a random 8-byte salt that is then hex-encoded
9.2.2。 TextEncryptor
使用Encryptors.text工廠方法構建標準的TextEncryptor:
Encryptors.text("password", "salt");
TextEncryptor使用標準BytesEncryptor來加密文本數據。加密結果做爲十六進制編碼字符串返回,以便在文件系統或數據庫中輕鬆存儲。
使用Encryptors.queryableText工廠方法構造一個「queryable」TextEncryptor:
Encryptors.queryableText("password", "salt");
可查詢的TextEncryptor和標準TextEncryptor之間的區別與初始化向量(iv)處理有關。在可查詢的TextEncryptor#encrypt操做中使用的iv是共享的,或者是常量的,不是隨機生成的。這意味着屢次加密的相同文本將老是產生相同的加密結果。這不太安全,但須要查詢的加密數據是必需的。可查詢的加密文本的一個例子是OAuth apiKey。
9.3。關鍵發電機
KeyGenerators類爲構建不一樣類型的密鑰生成器提供了許多便利的工廠方法。使用這個類,能夠建立一個BytesKeyGenerator來生成byte []鍵。您還能夠構造一個StringKeyGenerator來生成字符串鍵。 KeyGenerators是線程安全的。
9.3.1。 BytesKeyGenerator
使用KeyGenerators.secureRandom工廠方法生成由SecureRandom實例支持的BytesKeyGenerator:
KeyGenerator generator = KeyGenerators.secureRandom(); byte[] key = generator.generateKey();
默認密鑰長度爲8字節。還有一個KeyGenerators.secureRandom變體,能夠控制關鍵長度:
KeyGenerators.secureRandom(16);
使用KeyGenerators.shared工廠方法來構造一個BytesKeyGenerator,它在每次調用時老是返回相同的鍵:
KeyGenerators.shared(16);
9.3.2。 StringKeyGenerator
使用KeyGenerators.string工廠方法構造一個8字節的SecureRandom KeyGenerator,它將每一個鍵做爲字符串進行十六進制編碼:
KeyGenerators.string();
9.4。密碼編碼
spring-security-crypto模塊的密碼包提供對密碼編碼的支持。 PasswordEncoder是中央服務接口,具備如下簽名:
public interface PasswordEncoder { String encode(String rawPassword); boolean matches(String rawPassword, String encodedPassword); }
若是rawPassword一旦編碼,則等於encodePassword,則matches方法返回true。該方法旨在支持基於密碼的身份驗證方案。
BCryptPasswordEncoder實現使用普遍支持的「bcrypt」算法對密碼進行散列。 Bcrypt使用隨機16字節的鹽值,是一種故意慢的算法,以阻止密碼破解者。可使用「強度」參數來調整其工做量,該參數的取值範圍爲4到31.值越高,計算散列的工做就越多。默認值爲10.您能夠在部署的系統中更改此值,而不會影響現有密碼,由於該值也存儲在編碼散列中。
// Create an encoder with strength 16 BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16); String result = encoder.encode("myPassword"); assertTrue(encoder.matches("myPassword", result));
10. 併發支持
在大多數環境中,Security以每一個Thread爲基礎存儲。這意味着當一個新線程完成工做時,SecurityContext將丟失。 Spring Security提供了一些基礎設施,能夠幫助用戶輕鬆實現。 Spring Security提供了在多線程環境中使用Spring Security的低級抽象。實際上,這就是Spring Security創建在與AsyncContext.start(Runnable)和Spring MVC Async Integration集成的基礎之上。
10.1。 DelegatingSecurityContextRunnable
Spring Security的併發支持中最基礎的構建塊之一是DelegatingSecurityContextRunnable。它包裝一個委託Runnable,以便爲委託初始化具備指定SecurityContext的SecurityContextHolder。而後它調用委託Runnable確保之後清除SecurityContextHolder。 DelegatingSecurityContextRunnable看起來像這樣:
public void run() { try { SecurityContextHolder.setContext(securityContext); delegate.run(); } finally { SecurityContextHolder.clearContext(); } }
雖然很是簡單,它能夠無縫地將SecurityContext從一個線程傳輸到另外一個線程。這是很是重要的,由於在大多數狀況下,SecurityContextHolder會以每一個線程爲基礎。例如,您可能已經使用Spring Security的<global-method-security>支持來保護您的某個服務。您如今能夠輕鬆地將當前線程的SecurityContext傳輸到調用安全服務的線程。下面能夠找到一個能夠作到這一點的例子:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; SecurityContext context = SecurityContextHolder.getContext(); DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable, context); new Thread(wrappedRunnable).start();
上述代碼執行如下步驟:
- 建立一個能夠調用咱們的安全服務的Runnable。請注意,它不知道Spring Security
- 獲取咱們但願從SecurityContextHolder使用的SecurityContext,並初始化DelegatingSecurityContextRunnable
- 使用DelegatingSecurityContextRunnable建立一個線程
- 啓動咱們建立的線程
因爲使用SecurityContextHolder的SecurityContext建立一個DelegatingSecurityContextRunnable是很常見的,所以它有一個快捷方式構造函數。如下代碼與上述代碼相同:
Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; DelegatingSecurityContextRunnable wrappedRunnable = new DelegatingSecurityContextRunnable(originalRunnable); new Thread(wrappedRunnable).start();
咱們使用的代碼很簡單,可是它仍然須要咱們使用Spring Security的知識。在下一節中,咱們將介紹如何使用DelegatingSecurityContextExecutor來隱藏咱們使用Spring Security的事實。
10.2。 DelegatingSecurityContextExecutor
在上一節中,咱們發現很容易使用DelegatingSecurityContextRunnable,可是因爲咱們必須瞭解Spring Security才能使用它,這並不理想。咱們來看看DelegatingSecurityContextExecutor如何能夠屏蔽咱們的代碼,不知道咱們使用的是什麼Spring Security。
DelegatingSecurityContextExecutor的設計與DelegatingSecurityContextRunnable很是類似,除了它接受一個委託Executor而不是一個委託Runnable。您能夠在下面看到如何使用它的示例:
SecurityContext context = SecurityContextHolder.createEmptyContext(); Authentication authentication = new UsernamePasswordAuthenticationToken("user","doesnotmatter", AuthorityUtils.createAuthorityList("ROLE_USER")); context.setAuthentication(authentication); SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor, context); Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable);
代碼執行如下步驟:
- 建立要用於咱們的DelegatingSecurityContextExecutor的SecurityContext。請注意,在本示例中,咱們只需手動建立SecurityContext。然而,在什麼地方或如何得到SecurityContext並不重要(即若是咱們想要的話,咱們能夠從SecurityContextHolder獲取它)。
- 建立一個負責執行提交的「Runnable」的delegateExecutor
- 最後,咱們建立一個DelegatingSecurityContextExecutor,它負責使用DelegatingSecurityContextRunnable將任何被傳遞到execute方法的Runnable進行包裝。而後將包裝的Runnable傳遞給delegateExecutor。在這種狀況下,相同的SecurityContext將被用於提交給咱們的DelegatingSecurityContextExecutor的每一個Runnable。若是咱們運行後臺任務須要由具備提高的權限的用戶運行,這是很好的。
- 在這一點上,您可能會問本身「這是如何屏蔽我對Spring Security知識的代碼?咱們能夠注入一個已經初始化的DelegatingSecurityContextExecutor實例,而不是在咱們本身的代碼中建立SecurityContext和DelegatingSecurityContextExecutor。
@Autowired private Executor executor; // becomes an instance of our DelegatingSecurityContextExecutor public void submitRunnable() { Runnable originalRunnable = new Runnable() { public void run() { // invoke secured service } }; executor.execute(originalRunnable); }
如今咱們的代碼不知道SecurityContext被傳播到Thread,而後執行originalRunnable,而後SecurityContextHolder被清除。在此示例中,正在使用相同的用戶來執行每一個線程。當咱們調用executor.execute(Runnable)(即當前登陸的用戶)來處理originalRunnable時,若是咱們想使用SecurityContextHolder中的用戶呢?這能夠經過從DelegatingSecurityContextExecutor構造函數中刪除SecurityContext參數來完成。例如:
SimpleAsyncTaskExecutor delegateExecutor = new SimpleAsyncTaskExecutor(); DelegatingSecurityContextExecutor executor = new DelegatingSecurityContextExecutor(delegateExecutor);
如今隨時執行executor.execute(Runnable),SecurityContext首先由SecurityContextHolder獲取,而後使用SecurityContext建立咱們的DelegatingSecurityContextRunnable。這意味着咱們正在使用與用於調用executor.execute(Runnable)代碼的用戶相同的Runnable。
10.3。 Spring安全併發類
有關Java併發API和Spring任務抽象的更多集成,請參考Javadoc。一旦您瞭解之前的代碼,他們就很是自我解釋。
- DelegatingSecurityContextCallable
- DelegatingSecurityContextExecutor
- DelegatingSecurityContextExecutorService
- DelegatingSecurityContextRunnable
- DelegatingSecurityContextScheduledExecutorService
- DelegatingSecurityContextSchedulingTaskExecutor
- DelegatingSecurityContextAsyncTaskExecutor
- DelegatingSecurityContextTaskExecutor
11. Spring MVC集成
Spring Security提供了一些與Spring MVC的可選集成。本節將進一步詳細介紹整合。
11.1。 @EnableWebMvcSecurity
要使Spring Security與Spring MVC集成,請將@EnableWebMvcSecurity註釋添加到您的配置中。一個典型的例子是這樣的:
@Configuration @EnableWebMvcSecurity public class SecurityConfig { // ... }
11.2。 @AuthenticationPrincipal
Spring Security提供了AuthenticationPrincipalArgumentResolver,它能夠自動解析Spring MVC參數的當前Authentication.getPrincipal()。經過使用@EnableWebMvcSecurity,您將自動將其添加到您的Spring MVC配置中。若是您使用基於XML的配置,您必須本身添加。
一旦AuthenticationPrincipalArgumentResolver正確配置,您能夠在Spring MVC層中徹底與Spring Security進行解耦。
考慮一種自定義UserDetailsService返回實現UserDetails的對象和您本身的CustomUser對象的狀況。可使用如下代碼訪問當前驗證的用戶的CustomUser:
import org.springframework.security.web.bind.annotation.AuthenticationPrincipal; // ... @RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); CustomUser custom = (CustomUser) authentication == null ? null : authentication.getPrincipal(); // .. find messags for this user and return them ... }
從Spring Security 3.2開始,咱們能夠經過添加註釋來更直接的解決這個論點。例如:
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@AuthenticationPrincipal CustomUser customUser) { // .. find messags for this user and return them ... }
咱們能夠經過將@AuthenticationPrincipal做爲咱們本身的註釋的元註釋來進一步消除對Spring Security的依賴。下面咱們演示如何在一個名爲@CurrentUser的註釋上作到這一點。
重要的是要意識到,爲了消除對Spring Security的依賴,消費應用程序將建立@CurrentUser。 此步驟不是嚴格要求的,但有助於將您對Spring Security的依賴性隔離到更中心的位置。
@Target({ElementType.PARAMETER, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @AuthenticationPrincipal public @interface CurrentUser {}
如今已經指定了@CurrentUser,咱們可使用它來發出信號來解決咱們當前認證用戶的CustomUser。 咱們也將咱們對Spring Security的依賴性隔離成一個文件。
@RequestMapping("/messages/inbox") public ModelAndView findMessagesForUser(@CurrentUser CustomUser customUser) { // .. find messags for this user and return them ... }
11.3。 Spring MVC異步集成
Spring Web MVC 3.2+對異步請求處理有很好的支持。 沒有其餘配置,Spring Security將自動將SecurityContext設置爲執行控制器返回的Callable的Thread。 例如,如下方法將自動使用Callable在Callable建立時可用的SecurityContext執行:
@RequestMapping(method=RequestMethod.POST) public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public Object call() throws Exception { // ... return "someView"; } }; }
將SecurityContext與Callable關聯
技術上來講,Spring Security與WebAsyncManager集成。 用於處理Callable的SecurityContext是在調用startCallableProcessing時SecurityContextHolder上存在的SecurityContext。
與控制器返回的DeferredResult不進行自動整合。 這是由於DeferredResult由用戶處理,所以沒法自動與其集成。 可是,您仍然可使用[併發支持]來提供與Spring Security的透明集成。
11.4。 Spring MVC和CSRF集成
Spring Security將在使用Spring MVC表單標籤的表單中自動包含CSRF令牌。 例如,如下JSP:
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" xmlns:c="http://java.sun.com/jsp/jstl/core" xmlns:form="http://www.springframework.org/tags/form" version="2.0"> <jsp:directive.page language="java" contentType="text/html" /> <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en"> <!-- ... --> <c:url var="logoutUrl" value="/logout"/> <form:form action="${logoutUrl}" method="post"> <input type="submit" value="Log out" /> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form:form> <!-- ... --> </html> </jsp:root>
Will output HTML that is similar to the following:
<!-- ... --> <form action="/context/logout" method="post"> <input type="submit" value="Log out"/> <input type="hidden" name="_csrf" value="f81d4fae-7dec-11d0-a765-00a0c91e6bf6"/> </form> <!-- ... -->