首先描述最簡(jiǎn)單的身份驗(yàn)證后臺(tái)的處理過程
1、 用戶輸入需要確認(rèn)自己身份的信息,如賬號(hào)與密碼或許需要其它信息
2、 也許你希望提供多種有方式的驗(yàn)證形式,由系統(tǒng)選擇最合適的那一種,如從數(shù)據(jù)庫(DAO)或是通過身份驗(yàn)證中心的服務(wù)(ACS)統(tǒng)一管理等
3、 根據(jù)賬號(hào)找到指定源中的用戶、密碼及所擁有的權(quán)限(如果沒找到就拋出異常)等用戶的身份信息,驗(yàn)證用戶輸入的信息與源中返回的信息是否一致(如密碼)
4、 為了不至于每次都重復(fù)上面的過程,還要將驗(yàn)證后的信息緩存起來,如放到session中
與acegi結(jié)合分析
1、 用戶提交后為了能進(jìn)行上述的處理,acegi不是通過請(qǐng)求-影響模式,而是能過Servlet的Filter實(shí)現(xiàn)的。可以理解成這個(gè)身份驗(yàn)證的主入口,不同的處理機(jī)制采用不同的Filter,類名的規(guī)則為*ProcessingFilter,這些類全部都繼承至javax.servlet.Filter接口。下面是幾個(gè)常用的過濾器
模塊
|
功能
|
BasicProcessingFilter
|
按照RFC1945處理基本的身份驗(yàn)證請(qǐng)求
|
CasProcessingFilter
|
處理耶魯大學(xué)的中心身份驗(yàn)證服務(wù)器(CAS)許可
|
AnonymousProcessingFilter
|
對(duì)匿名身份的處理
|
AuthenticationProcessingFilter
|
處理類似于Servlet規(guī)范的j_security_check的Http form post
|
在Spring的配置過程中有些過濾器除了自身的信息外,還有可能需要切入點(diǎn),以便切入到其它模塊中的配置信息。如AuthenticationProcessingFilter驗(yàn)證不通過的處理頁面或要采用的協(xié)議、CasProcessingFilter需要提交到CAS上的其他信息。它們的統(tǒng)一接口類為AuthenticationEntryPoint在此要注意的是,切入點(diǎn)與過濾器之間有著密切的聯(lián)系與對(duì)應(yīng)關(guān)系的。

2、 acegi為了實(shí)現(xiàn)多種方式的驗(yàn)證形式提供了AuthenticationManager接口,目的是對(duì)多種驗(yàn)證形式進(jìn)行管理,并通過指定的驗(yàn)證形式返回用戶的身份。所以上述的過濾器均會(huì)調(diào)用該接口的authenticate(Authentication authentication)方法以返回當(dāng)前用戶的身份信息。該接口主要的實(shí)現(xiàn)類是ProviderManager

AuthenticationProvider(身份驗(yàn)證供應(yīng)器)接口有很多實(shí)現(xiàn)類,每個(gè)都致力于處理一個(gè)特定身份驗(yàn)證的具體實(shí)現(xiàn),AuthenticationManager負(fù)責(zé)輪詢AuthenticationProvider列表,并使用第一個(gè)能夠處理給定的Authentication請(qǐng)求對(duì)象的AuthenticationProvider。身份驗(yàn)證供應(yīng)器也就是上面說的驗(yàn)證形式。最常用的是DaoAuthenticationProvider,實(shí)現(xiàn)從數(shù)據(jù)庫中獲取用戶的身份信息

3、 在acegi中用戶的身份信息存放到以Authentication接口的實(shí)現(xiàn)類的實(shí)例中。在介紹該接口之前先要了解一些關(guān)鍵術(shù)語
1) principal(主體)指可以執(zhí)行操作的用戶、服務(wù)或代理(agent),即身份驗(yàn)證的發(fā)啟者
2) credential(憑證)指主體提供密碼之類用于身份驗(yàn)證(authentication)的信息戳
3) authentication(身份驗(yàn)證)確定調(diào)用者身份的過程
4) authorization(授權(quán))指決定哪個(gè)主體準(zhǔn)許執(zhí)行給定操作的過程
Authentication接口包含主體標(biāo)示、主體的憑證以及主體所擁有的一組權(quán)限。

Authentication接口繼承java.security.Principal接口,因此Authentication天生就要滿足主體的類型。該接口實(shí)際上是上述幾個(gè)關(guān)鍵詞對(duì)象化的一個(gè)容器,它存儲(chǔ)了身份驗(yàn)證的全部信息。由此會(huì)發(fā)現(xiàn)它與AuthenticationProvider(身份驗(yàn)證供應(yīng)器)有著天然的聯(lián)系,如對(duì)于數(shù)據(jù)庫的供應(yīng)器它只要記錄用戶的帳號(hào)和密碼,但對(duì)于CAS它還可能還要記錄服務(wù)器響應(yīng)的列表,或是與認(rèn)證相關(guān)的其它信息。因此該接口對(duì)應(yīng)身份驗(yàn)證供應(yīng)器有多個(gè)實(shí)現(xiàn)類。正因?yàn)橛诖耍械膶?shí)現(xiàn)類對(duì)于開發(fā)者來說幾乎是隱藏在,可以理解成工具或是服務(wù)類

下面對(duì)Authentication接口中的方法做詳細(xì)擴(kuò)展說明
1) getPrincipal(獲得主體)與getCredential(獲得憑證),它們的設(shè)置過程:無論何種驗(yàn)證形式用戶都是通過用戶輸入帳號(hào)和密碼開始的,過濾器會(huì)攔截用戶的請(qǐng)求,由驗(yàn)證管理器(AuthenticationManager)找到合適的身份驗(yàn)證供應(yīng)器(AuthenticationProvider)提供身份驗(yàn)證。在AuthenticationManager接口只有一個(gè)方法authenticate(…),而該方法的參數(shù)與返回類型均是Authentication。所以大多數(shù)過濾器在請(qǐng)求身份驗(yàn)證前總是創(chuàng)建一個(gè)簡(jiǎn)單的UsernamePasswordAuthenticationToken(該類是Authentication接口的子類)對(duì)象,將其作為AuthenticationManager.authenticate(…)方法中的參數(shù),再由AuthenticationProvider依據(jù)自身的特點(diǎn)創(chuàng)建相應(yīng)的Authentication接口子類的實(shí)例,因此身份驗(yàn)證的具體信息也是在這最后一步完成的,即是在AuthenticationProvider中被創(chuàng)建與賦值的。其中就包括主體與憑證。如對(duì)于數(shù)據(jù)庫來說也是賬號(hào)與密碼。
2) getAuthorities()得到當(dāng)前用戶所擁有的全部權(quán)限。Acegi提供一個(gè)GrantedAuthority接口,用于對(duì)應(yīng)應(yīng)用系統(tǒng)中有真正意義的權(quán)限實(shí)現(xiàn)。常用的是GrantedAuthorityImpl,它存儲(chǔ)主體的已獲授權(quán)的String表示,即權(quán)限也就是字符串的表示。而GrantedAuthority僅對(duì)字符做一層簡(jiǎn)單的封裝。
3) getDetails()獲取詳細(xì)資料,目的是得到當(dāng)前用戶的詳細(xì)信息,如在應(yīng)用系統(tǒng)可以是用戶的一個(gè)Bean(所在的部門,真實(shí)姓名…)。為此Acegi提供了一個(gè)UserDetails接口.

l
isAccountNonExpired 帳號(hào)是否未過期
l
isAccountNonLocked 帳號(hào)是否未鎖定
l
isCredentialsNonExpired密碼是否未過期
l
isEnabled 是否可用(激活)
我們會(huì)發(fā)現(xiàn)對(duì)于一個(gè)應(yīng)用系統(tǒng)來說,用戶的詳細(xì)信息中除與身份驗(yàn)證相關(guān)的信息外還要很多其它的信息,如用戶所在的部門、電話…。為此acegi提供了另一個(gè)接口AuthenticationDetailsSource,它僅提供一個(gè)方法buildDetails(HttpServletRequest
request)用以創(chuàng)建客戶化的用戶信息。在過濾器中在執(zhí)行身份驗(yàn)證之前調(diào)用該方法。
4) isAuthenticated(),是否通過了身份驗(yàn)證
當(dāng)通過主體(帳號(hào))從源(如DAO、CAS、JAAS…)中返回的信息中創(chuàng)建出Authentication實(shí)例后,還要校驗(yàn)用戶的憑證(密碼)的一致性。對(duì)于提供了以PasswordEncoder接口為代表的一組密碼編碼器的實(shí)現(xiàn)

4、 在我們討論對(duì)于身份驗(yàn)證(Authentication)對(duì)象的保存之前,先來說明一下對(duì)它的訪問。為了確保任何對(duì)身份驗(yàn)證感興趣的類型都可以訪問它,acegi通過SecurityContextHolder(該類中提供的所有成員方法均是靜態(tài)的)用來保持SecurityContext的ThreadLocal對(duì)象。

通過SecurityContext類圖可以看出在它很簡(jiǎn)單只是對(duì)Authentication對(duì)象的一個(gè)引用,之所以又做了這樣一層包裝的目的是為了客戶可以做任意的擴(kuò)展。由于SecurityContextHolder引用的是線程ThreadLocal集合,而對(duì)于B/S系統(tǒng)來說,一個(gè)線程的生命期還是太短了,無法保證SecurityContext對(duì)象的信息在一個(gè)線程執(zhí)行后不會(huì)被JVM垃圾回收。一般來說我們會(huì)將SecurityContext放到session中,但這樣又無法保證在邏輯層中訪問到session。對(duì)此acegi的解決方案是通過javax.servlet.Filter使不同與的訪問范圍與SecurityContextHolder做整合(以session為例,每次請(qǐng)求過濾器都會(huì)從session中取得SecurityContext然后再將其賦到SecurityContextHolder)。Acegi提供了多種整合過濾器,命名規(guī)則為*IntegrationFilter
模塊
|
功能
|
HttpSessionContextIntegrationFilter
|
請(qǐng)求之間使用HttpSession存儲(chǔ)SecurityContext
|
HttpRequestIntegrationFilter
|
從HttpServletRequest.getUserPrincipal()獲得身份驗(yàn)證,但在請(qǐng)求結(jié)束時(shí)不能將該身份驗(yàn)證寫回該位置
|
JbossIntegrationFilter
|
從Jboss的java:comp/env/security/subject的JNDI位置獲取身份驗(yàn)證,但在請(qǐng)求結(jié)束時(shí)不能將該身份驗(yàn)證寫回該位置
|
接下來再來討論對(duì)Authentication的設(shè)置,當(dāng)處理過濾器調(diào)用AuthenticationManager. Authentication(…)獲得Authentication對(duì)象后,它會(huì)調(diào)用自身的成員方法successfulAuthentication(…)將其封裝為SecurityContext并設(shè)置到SecurityContextHolder中。

注意successfulAuthentication(…)方法是在抽象類AbstractProcessingFilter中,而有一些處理過程器并沒有繼承該類,所以只有AbstractProcessingFilter的子類才會(huì)在當(dāng)前線程中取得SecurityContext對(duì)象。
再來描述授權(quán)的后臺(tái)的處理過程
1、 我們可能希望在三個(gè)地方做安全驗(yàn)證,即是否可以訪問當(dāng)前頁面,是否可以調(diào)用當(dāng)前方法,是否可以控制當(dāng)前方法參數(shù)的對(duì)象域
2、 對(duì)于安全驗(yàn)證的判斷可能希望提供不同的表決策略,如一票否決制,還是有一票同意就通過或是同意大于否決票時(shí)才通過。
3、 總之,所有需要的處理無非是在做這樣的工作。當(dāng)前用戶所擁有的權(quán)限中,否則有(或匹配)與當(dāng)前被調(diào)用者(頁面、方法、域)所指定的權(quán)限
Acegi對(duì)于授權(quán)的技術(shù)實(shí)現(xiàn)
從技術(shù)上話,授權(quán)完全與日志一樣是一個(gè)橫切關(guān)注點(diǎn),與具體業(yè)務(wù)沒有任何關(guān)系。而Acegi只是借助Spring的AOP與javax.servlet.Filter,對(duì)這授權(quán)方面的攔截。對(duì)于攔截器與其持有的對(duì)象Acegi提供如下實(shí)現(xiàn):
攔截器
|
描述
|
持有的對(duì)象
|
描述
|
AbstractSecurityInterceptor
|
是所有攔截器的抽象父類,實(shí)際授權(quán)的全部驗(yàn)證過程均在該類beforeInvocation()與afterInvocation()兩個(gè)方法中完成,子類只是區(qū)別不同類型的安全對(duì)象。
|
FilterSecurityInterceptor
|
是一個(gè)web的過濾器,用于對(duì)頁面(URL)的授權(quán)驗(yàn)證
|
FilterInvocation
|
僅是對(duì)簡(jiǎn)單crequest、response與FilterChain包裝的實(shí)現(xiàn)
|
MethodSecurityInterceptor
|
用于驗(yàn)證Spring容器中bean的方法,并且需要使用Spring中的代理
|
MethodInvocation
|
AOP Alliance提供的被代理對(duì)象接口,運(yùn)行時(shí)的實(shí)際對(duì)象類型是由Spring提供的ReflectiveMethodInvocation或CglibMethodInvocation
|
AspectJSecurityInterceptor
|
該類是在指定切入點(diǎn)調(diào)用AspectJ
|
JoinPoint AspectJCallback
|
執(zhí)行AspectJ的回調(diào)
|
|
|
|
|
|
AbstractSecurityInterceptor的執(zhí)行過程:
首先查詢應(yīng)用于該調(diào)用的配置屬性。如果沒有任何配置屬性,該調(diào)用被認(rèn)為是公有的,并且繼續(xù)進(jìn)行該調(diào)用。如果找到配置屬性,包含在SecurityContextHolder中的Authentication是通過AuthenticationManager驗(yàn)證的,并且請(qǐng)求AccessDecisionManager來批準(zhǔn)該請(qǐng)求,如果成功RunAsManager可以替代該Authentication的標(biāo)識(shí),然后繼續(xù)進(jìn)行該調(diào)用。調(diào)用完成時(shí),將通過更新SecurityContextHolder來包含實(shí)際的身份驗(yàn)證對(duì)象以清除RunAsManager的替代標(biāo)識(shí)。最后調(diào)用AfterInvocationManager,如果定義了的話。
RunAsManager:該接口的主要作用成員方法是Authentication
buildRunAs(Authentication authentication, Object object,
ConfigAttributeDefinition config)目的是當(dāng)執(zhí)行特定操作時(shí)用于選擇性的替換Authentication對(duì)象,該方法是在安全驗(yàn)證之后,安全對(duì)象執(zhí)行之前被調(diào)用。在安全對(duì)象執(zhí)行之后該方法對(duì)Authentication對(duì)象替換后被還原。
AfterInvocationManager:作用是修改從安全對(duì)象調(diào)用中返回的對(duì)象。這通常被用來過濾僅針對(duì)已包含、已驗(yàn)證元素的集合,或者如果信息是受保護(hù)的,轉(zhuǎn)換實(shí)例變量。如果該主體沒有針對(duì)返回對(duì)象的權(quán)限,它還可以拋出AccessDeniedException。主要的方法是
Object decide(Authentication
authentication, Object object, ConfigAttributeDefinition config, Object returnedObject) throws
AccessDeniedException,它需要返回一個(gè)對(duì)象,然后該對(duì)象將變成安全對(duì)象調(diào)用的結(jié)果。輪詢AfterInvocationProvider接口的具體類。
最后分析權(quán)限表決的原理,還是讓我們虛擬這樣一個(gè)場(chǎng)景:
1、 要求有兩個(gè)表決器,1)判斷當(dāng)前用戶是否為超級(jí)管理員,2)判斷當(dāng)前用戶是否擁有當(dāng)前所調(diào)用的方法或頁面的權(quán)限
2、 如果其中任何一個(gè)表決器投出贊成票,就認(rèn)為通過。這是一種表決策略
與Acegi結(jié)合分析
2、表決策略作用是輪詢所有的表決器,根據(jù)當(dāng)前策略結(jié)合表決器的表決結(jié)果,做出最終判斷結(jié)果。Acegi表決策略管理的接口是

1) decide(…)根據(jù)當(dāng)前用戶所擁有的權(quán)限信息[authentication],攔截器所持有的對(duì)象[object]以及在配置文件中所持有對(duì)象所必要的權(quán)限s[config],輪詢表決器做出決策。
2) supports(ConfigAttribute)判斷所有表決器是否有一個(gè)以上支持這種權(quán)限的表示形式
3) supports(Class) 判斷所有表決器是否有一個(gè)以上支持?jǐn)r截器所持有的對(duì)象類型
后兩個(gè)方法主要在是各種攔截器初始化時(shí)調(diào)用,以但Spring驗(yàn)證bean定義的合法性
對(duì)于表決策略來說最終可能會(huì)得到三個(gè)結(jié)果中的一個(gè)贊成、否決或是棄權(quán)(結(jié)果是由表決器結(jié)合策略得到的)。對(duì)于棄權(quán)的產(chǎn)生是由于攔截器所持有的對(duì)象在配置中沒有設(shè)置任何權(quán)限。為此該接口的抽象子類提供allowIfAllAbstainDecisions開關(guān),如果所有表決器投出的均為棄權(quán)票,表示沒有通過授權(quán)則將該屬性設(shè)為true,默認(rèn)為false.
Acegi提供了多種表決策略

注意:一個(gè)表決器只能投一票
AffirmativeBased 有一個(gè)決策器投出贊成票就通過
ConsensusBased 贊成票大于否決票就通過,相同也算是沒有通過授權(quán)
UnanimousBased 一票否決制,也就是只要有一個(gè)表決器投否決票就算不通過
因此對(duì)于上面的場(chǎng)景我們應(yīng)該選擇AffirmativeBased表決策略,在決策略下還要有兩個(gè)表決器,下面介紹表決器
1、