【IT168 專稿】對(duì)于任何一個(gè)完整的應(yīng)用系統(tǒng),完善的 認(rèn)證和授權(quán)機(jī)制是必不可少的。Acegi Security(以下簡(jiǎn)稱Acegi)是一個(gè)能為基于Spring的企業(yè)應(yīng)用提供強(qiáng)大而靈活安全訪問(wèn)控制解決方案的框架,Acegi已經(jīng)成為 Spring官方的一個(gè)子項(xiàng)目,所以也稱為Spring Security。它通過(guò)在Spring容器中配置一組Bean,充分利用Spring的IoC和AOP功能,提供聲明式安全訪問(wèn)控制的功能。雖然,現(xiàn)在 Acegi也可以應(yīng)用到非Spring的應(yīng)用程序中,但在Spring中使用Acegi是最自然的方式。
Acegi可以實(shí)現(xiàn)業(yè)務(wù)對(duì)象方法級(jí)的安全訪問(wèn)控制粒度,它提供了以下三方面的應(yīng)用程序的安全:
? URL資源的訪問(wèn)控制
如所有用戶(包括其名用戶)可以訪問(wèn)index.jsp登錄頁(yè)面,而只有授權(quán)的用戶可以訪問(wèn)/user/addUser.jsp頁(yè)面。Acegi允許通過(guò)正則表達(dá)式或Ant風(fēng)格的路徑表達(dá)式定義URL模式,讓授權(quán)用戶訪問(wèn)某一URL匹配模式下的對(duì)應(yīng)URL資源。
?
業(yè)務(wù)類方法的訪問(wèn)控制
Spring容器中所有Bean的方法都可以被Acegi管理,如所有用戶可以調(diào)用BbtForum#getRefinedTopicCount()方法,而只有授權(quán)用戶可以調(diào)用BbtForum#addTopic()方法。
? 領(lǐng)域?qū)ο蟮脑L問(wèn)控制
業(yè)務(wù)類方法代表一個(gè)具體的業(yè)務(wù)操作,比如更改、刪除、審批等,業(yè)務(wù)類方法訪問(wèn)控制解決了用戶是否有調(diào)用某種操作的權(quán)限,但并未對(duì)操作的客體(領(lǐng)域?qū)ο螅┻M(jìn) 行控制。對(duì)于我們的論壇應(yīng)用來(lái)說(shuō),用戶可以調(diào)用BbtForum#updateUser(User user)方法更改用戶注冊(cè)信息,但應(yīng)該僅限于更改自己的用戶信息,也即調(diào)用BbtForum#updateUser()所操作的User這個(gè)領(lǐng)域?qū)ο蟊?須是受限的。
Acegi通過(guò)多個(gè)不同用途的Servlet過(guò)濾器對(duì)URL資源進(jìn)行保護(hù),在請(qǐng)求受保護(hù)的URL資源前,Acegi的Servlet過(guò)濾器判斷用戶是否有權(quán)訪問(wèn)目標(biāo)資源,授權(quán)者被開(kāi)放訪問(wèn),而未未被授權(quán)者將被阻擋在大門(mén)之外。
Acegi通過(guò)Spring AOP對(duì)容器中Bean的受控方法進(jìn)行攔截,當(dāng)用戶的請(qǐng)求引發(fā)調(diào)用Bean的受控方法時(shí),Acegi的方法攔截器開(kāi)始工作,阻止未授權(quán)者的調(diào)用。
對(duì)領(lǐng)域?qū)ο蟮脑L問(wèn)控制建立在對(duì)Bean方法保護(hù)的基礎(chǔ)上,在最終開(kāi)放目標(biāo)Bean方法的執(zhí)行前,Acegi將檢查用戶的ACL(Aeccess Control List:訪問(wèn)控制列表)是否包含正要進(jìn)行操作的領(lǐng)域?qū)ο?,只有領(lǐng)域?qū)ο蟊皇跈?quán)時(shí),用戶才可以使用Bean方法對(duì)領(lǐng)域?qū)ο筮M(jìn)行處理。此外,Acegi還可 以對(duì)Bean方法返回的結(jié)果進(jìn)行過(guò)濾,將一些不在當(dāng)前用戶訪問(wèn)權(quán)限范圍內(nèi)的領(lǐng)域?qū)ο筇蕹簟磦鹘y(tǒng)的數(shù)據(jù)可視域范圍的控制。一般來(lái)說(shuō),使用Acegi控 制數(shù)據(jù)可視域未非理想的選擇,相反通過(guò)傳統(tǒng)的動(dòng)態(tài)SQL的解決方案往往更加簡(jiǎn)單易行。
從本質(zhì)特性上來(lái)說(shuō),Servlet過(guò)濾器就是最原始的原生態(tài)AOP,所以我們可以說(shuō)Acegi不但對(duì)業(yè)務(wù)類方法、領(lǐng)域?qū)ο笤L問(wèn)控制采用了AOP技術(shù)方案, 對(duì)URL資源的訪問(wèn)控制也使用了AOP的技術(shù)方案。使用AOP技術(shù)方案的框架是令人振奮的,這意味著,開(kāi)發(fā)者可以在應(yīng)用程序業(yè)務(wù)功能開(kāi)發(fā)完畢后,輕松地通 過(guò)Acegi給應(yīng)用程序穿上安全保護(hù)的“鐵布衫”。
Acegi體系結(jié)構(gòu)
乘飛機(jī)前需要通過(guò)安檢,乘客必須提供身份證以驗(yàn)證其身份。在通過(guò)安檢進(jìn)入候機(jī)室后,國(guó)航、海航、南航等不同航空公司的飛機(jī)陸續(xù)到達(dá),但你只能登上機(jī)票上對(duì) 應(yīng)航班的飛機(jī)。在登機(jī)后,只能坐在機(jī)票對(duì)應(yīng)的座位上——你不能搶占他人的座位,你不能在座位上刻字留念、你不能要求空姐打開(kāi)機(jī)窗……
乘飛機(jī)的過(guò)程最能體現(xiàn)安全控制的流程,我們可以從中找到身份認(rèn)證、資源訪問(wèn)控制、領(lǐng)域?qū)ο蟀踩刂频膶?duì)應(yīng)物:安檢對(duì)應(yīng)身份認(rèn)證,登機(jī)對(duì)應(yīng)資源訪問(wèn)控制而按號(hào)就座則對(duì)應(yīng)領(lǐng)域?qū)ο蟀踩刂啤?
Acegi通過(guò)兩個(gè)組件對(duì)象完成以上安全問(wèn)題的處理:AuthenticationManager(認(rèn)證管理器)、AccessDecisionManager(訪問(wèn)控制管理器),如圖 1所示:

圖 1 Acegi體系結(jié)構(gòu)
SecurityContextHolder是框架級(jí)的容器,它保存著和所有用戶關(guān)聯(lián)SecurityContext實(shí)例, SecurityContext承載著用戶(也稱認(rèn)證主體)的身份信息的權(quán)限信息, AuthenticationManager、AccessDecisionManager將據(jù)此進(jìn)行安全訪問(wèn)控制。
SecurityContext的認(rèn)證主體安全信息在一個(gè)HTTP請(qǐng)求線程的多個(gè)調(diào)用之間是共享的(通過(guò)ThreadLocal),但它不能在多個(gè)請(qǐng)求之 間保持共享。為了解決這個(gè)問(wèn)題,Acegi將認(rèn)證主體安全信息緩存于HttpSession中,當(dāng)用戶請(qǐng)求一個(gè)受限的資源時(shí),Acegi通過(guò) HttpSessionContextIntegrationFilter將認(rèn)證主體信息從HttpSession中加載到 SecurityContext實(shí)例中,認(rèn)證主體關(guān)聯(lián)的SecurityContext實(shí)例保存在Acegi容器級(jí)的 SecurityContextHolder里。當(dāng)請(qǐng)求結(jié)束之后,HttpSessionContextIntegrationFilter執(zhí)行相反的操 作,將SecurityContext中的認(rèn)證主體安全信息重新轉(zhuǎn)存到HttpSession中,然后從SecurityContextHolder中清 除對(duì)應(yīng)的SecurityContext實(shí)例。通過(guò)HttpSession轉(zhuǎn)存機(jī)制,用戶的安全信息就可以在多個(gè)HTTP請(qǐng)求間共享,同時(shí)保證 SecurityContextHolder中僅保存當(dāng)前有用的用戶安全信息,其整體過(guò)程如圖 2所示:

圖 2 SecurityContext在HttpSession和請(qǐng)求線程間的轉(zhuǎn)交過(guò)程
當(dāng)用戶請(qǐng)求一個(gè)受限的資源時(shí),AuthenticationManager首先開(kāi)始工作,它象一個(gè)安檢入口,對(duì)用戶身份進(jìn)行核查,用戶必須提供身份認(rèn)證的 憑證(一般是用戶名/密碼)。在進(jìn)行身份認(rèn)證時(shí),AuthenticationManager將身份認(rèn)證的工作委托給多個(gè) AuthenticationProvider。因?yàn)樵诰唧w的系統(tǒng)中,用戶身份可能存儲(chǔ)在不同的用戶信息安全系統(tǒng)中(如數(shù)據(jù)庫(kù)、CA中心、LDAP服務(wù) 器),不同用戶信息安全系統(tǒng)需要不同的AuthenticationProvider執(zhí)行諸如用戶信息查詢、用戶身份判斷、用戶授權(quán)信息獲取等工作。只要 有一個(gè)AuthenticationProvider可以識(shí)別用戶的身份,AuthenticationManager就通過(guò)用戶身份認(rèn)證,并將用戶的授 權(quán)信息放入到SecurityContext中。
當(dāng)用戶通過(guò)身份認(rèn)證后,試圖訪問(wèn)某個(gè)受限的程序資源時(shí),AccessDecisionManager開(kāi)始工作。 AccessDecisionManager采用民主決策機(jī)制判斷用戶是否有權(quán)訪問(wèn)目標(biāo)程序資源,它包含了多個(gè)AccessDecisionVoter。 在訪問(wèn)決策時(shí)每個(gè)AccessDecisionVoter都擁有投票權(quán),AccessDecisionManager統(tǒng)計(jì)投票結(jié)果,并按照某種決策方式根 據(jù)這些投票結(jié)果決定最終是否向用戶開(kāi)放受限資源的訪問(wèn)。
、操作類都在這些組件類的基礎(chǔ)上進(jìn)行操作。在進(jìn)入Acegi框架的具體學(xué)習(xí)前,有必要事先了解一下這些承載Acegi框架重要概念的組件類。
首先,我們要接觸是UserDetails接口,它代表一個(gè)應(yīng)用系統(tǒng)的用戶,該接口定義了用戶安全相關(guān)的信息,如用戶名/密碼,用戶是否有效等信息,你可以根據(jù)以下接口方法進(jìn)行相關(guān)信息的獲?。?
String getUsername():獲取用戶名;
String getPassword():獲取密碼;
boolean isAccountNonExpired():用戶帳號(hào)是否過(guò)期;
boolean isAccountNonLocked():用戶帳號(hào)是否鎖定;
boolean isCredentialsNonExpired():用戶的憑證是否過(guò)期;
boolean isEnabled():用戶是否處于激活狀態(tài)。
當(dāng)以上任何一個(gè)判斷用戶狀態(tài)的方法都返回false時(shí),用戶憑證就被視為無(wú)效。
UserDetails還定義了獲取用戶權(quán)限信息的方法:GrantedAuthority[] getAuthorities(),GrantedAuthority代表用戶權(quán)限信息,它定義了一個(gè)獲取權(quán)限描述信息(以字符串表示,如 PRIV_COMMON)的方法:String getAuthority()。

圖 3 用戶和權(quán)限
在未使用Acegi之前,我們可能通過(guò)類似User、Customer等領(lǐng)域?qū)ο蟊硎居脩舻母拍睿⒃诔绦蛑芯帉?xiě)相應(yīng)的用戶認(rèn)證的邏輯?,F(xiàn)在,你要做的一 個(gè)調(diào)整是讓原先這些代表用戶概念的領(lǐng)域類實(shí)現(xiàn)UserDetails接口,這樣,Acegi就可以通過(guò)UserDetails接口訪問(wèn)到用戶的信息了。
UserDetails可能從數(shù)據(jù)庫(kù)、LDAP等用戶信息資源中返回,這要求有一種機(jī)制來(lái)完成這項(xiàng)工作,UserDetailsService正是充當(dāng)這 一角色的接口。UserDetailsService接口很簡(jiǎn)單,僅有一個(gè)方法:UserDetails loadUserByUsername(String username) ,這個(gè)方法通過(guò)用戶名獲取整個(gè)UserDetails對(duì)象。
Authentication代表一個(gè)和應(yīng)用程序交互的待認(rèn)證用戶,Acegi從類似于登錄頁(yè)面、Cookie等處獲取待認(rèn)證的用戶信息(一般是用戶名密碼)自動(dòng)構(gòu)造Authentication實(shí)例。

圖 4 Acegi的認(rèn)證用戶
Authentication可以通過(guò)Object getPrincipal()獲取一個(gè)代表用戶的對(duì)象,這個(gè)對(duì)象一般可以轉(zhuǎn)換為UserDetails,從中可以取得用戶名/密碼等信息。在 Authentication被AuthenticationManager認(rèn)證之前,沒(méi)有任何權(quán)限的信息。在通過(guò)認(rèn)證之后,Acegi通過(guò) UserDetails將用戶對(duì)應(yīng)的權(quán)限信息加載到Authentication中。Authentication擁有一個(gè) GrantedAuthority[] getAuthorities()方法,通過(guò)該方法可以得到用戶對(duì)應(yīng)的權(quán)限信息。
Authentication和UserDetails很容易被混淆,因?yàn)閮烧叨加杏脩裘?密碼及權(quán)限的信息,接口方法也很類似。其實(shí) Authentication是Acegi進(jìn)行安全訪問(wèn)控制真正使用的用戶安全信息的對(duì)象,它擁有兩個(gè)狀態(tài):未認(rèn)證和已認(rèn)證。UserDetails是代 表一個(gè)從用戶安全信息源(數(shù)據(jù)庫(kù)、LDAP服務(wù)器、CA中心)返回的真正用戶,Acegi需要將未認(rèn)證的Authentication和代表真實(shí)用戶的 UserDetails進(jìn)行匹配比較,通過(guò)匹配比較(簡(jiǎn)單的情況下是用戶名/密碼是否一致)后,Acegi將UserDetails中的其它安全信息(如 權(quán)限、ACL等)拷貝到Authentication中。這樣,Acegi安全控制組件在后續(xù)的安全訪問(wèn)控制中只和Authentication進(jìn)行交 互。
由于Acegi對(duì)程序資源進(jìn)行訪問(wèn)安全控制時(shí),一定要事先獲取和請(qǐng)求用戶對(duì)應(yīng)的Authentication,Acegi框架必須為Authentication提供一個(gè)“寓所”,以便在需要時(shí)直接從“寓所”把它請(qǐng)出來(lái),作為各種安全管理器決策的依據(jù)。
SecurityContextHolder就是Authentication容身的“寓所”,你可以通過(guò) SecurityContextHolder.getContext().getAuthenication()代碼獲取Authentication。 細(xì)心觀察一下這句代碼,你會(huì)發(fā)現(xiàn)在SecurityContextHolder和Authentication之間存在一個(gè)getContext()中 介,這個(gè)方法返回SecurityContext對(duì)象。SecurityContext這個(gè)半路殺出來(lái)的程咬金有什么特殊的用途呢?我們知道 Authentication是用戶安全相關(guān)的信息,請(qǐng)求線程其它信息(如登錄驗(yàn)證碼等)則放置在SecurityContext中,構(gòu)成了一個(gè)完整的安 全信息上下文。SecurityContext接口提供了獲取和設(shè)置Authentication的方法:
? Authentication getAuthentication()
? void setAuthentication(Authentication authentication)

圖 5 認(rèn)證用戶信息存儲(chǔ)器
SecurityContextHolder是Acegi框架級(jí)的對(duì)象,它在內(nèi)部通過(guò)ThreadLocal為請(qǐng)求線程提供線程綁定的 SecurityContext對(duì)象。這樣,任何參與當(dāng)前請(qǐng)求線程的Acegi安全管理組件、業(yè)務(wù)服務(wù)對(duì)象等都可以直接通過(guò) SecurityContextHolder.getContext()獲取線程綁定的SecurityContext,避免通過(guò)方法入?yún)⒌姆绞将@取用戶 相關(guān)的SecurityContext。
線程綁定模式對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō)是適合的,但是應(yīng)用本身會(huì)創(chuàng)建其它的線程,那么只有主線程可以獲得線程綁定SecurityContext,而主線程衍生 出的新線程則無(wú)法得到線程綁定的SecurityContext。Acegi考慮到了這些不同應(yīng)用情況,提供了三種綁定SecurityContext的 模式:
? SecurityContextHolder.MODE_THREADLOCAL:SecurityContext綁定到主線程,這是默認(rèn)的模式;
? SecurityContextHolder.MODE_GLOBAL:SecurityContext綁定到JVM中,所有線程都使用同一個(gè)SecurityContext;
? SecurityContextHolder.MODE_INHERITABLETHREADLOCAL::SecurityContext綁定到主線程及由主線程衍生的線程中。
你可以通過(guò)SecurityContextHolder.setStrategyName(String strategyName)方法指定SecurityContext的綁定模式。
用戶認(rèn)證過(guò)程
Acegi支持多種方式的用戶認(rèn)證:如典型的基于數(shù)據(jù)庫(kù)的認(rèn)證、基于LDAP的認(rèn)證、基于Yale中心認(rèn)證等方式。不同的認(rèn)證環(huán)境擁有不同的用戶認(rèn)證方式,現(xiàn)在我們先拋開(kāi)這些具體的細(xì)節(jié),考察一下Acegi對(duì)受限資源進(jìn)行訪問(wèn)控制的典型過(guò)程:
1.你點(diǎn)擊一個(gè)鏈接訪問(wèn)一個(gè)網(wǎng)頁(yè);
2.瀏覽器發(fā)送一個(gè)請(qǐng)求到服務(wù)器,服務(wù)器判斷出你正在訪問(wèn)一個(gè)受保護(hù)的資源;
3.如果此時(shí)你并未通過(guò)身份認(rèn)證,服務(wù)器發(fā)回一個(gè)響應(yīng)提示你進(jìn)行認(rèn)證——這個(gè)響應(yīng)可能是一個(gè)HTTP響應(yīng)代碼,抑或重定向到一個(gè)指定頁(yè)面;
4.根據(jù)系統(tǒng)使用認(rèn)證機(jī)制的不同,瀏覽器或者重定向到一個(gè)登錄頁(yè)面中,或者由瀏覽器通過(guò)一些其它的方式獲取你的身份信息(如通過(guò)BASIC認(rèn)證對(duì)話框、一個(gè)Cookie或一個(gè)X509證書(shū));
5.瀏覽器再次將用戶身份信息發(fā)送到服務(wù)器上(可能是一個(gè)用戶登錄表單的HTTP POST信息、也可能是包含認(rèn)證信息的HTTP報(bào)文頭);
6.服務(wù)器判斷用戶認(rèn)證信息是否有效,如果無(wú)效,一般情況下,瀏覽器會(huì)要求你繼續(xù)嘗試,這意味著返回第3步。如果有效,則到達(dá)下一步;
7.服務(wù)器重新響應(yīng)第2步所提交的原始請(qǐng)求,并判斷該請(qǐng)求所訪問(wèn)的程序資源是否在你的權(quán)限范圍內(nèi),如果你有權(quán)訪問(wèn),請(qǐng)求將得到正確的執(zhí)行并返回結(jié)果。否則,你將收到一個(gè)HTTP 403錯(cuò)誤,這意味著你被禁止訪問(wèn)。
在Acegi框架里,你可以找到對(duì)應(yīng)以上大多數(shù)步驟的類,其中ExceptionTranslationFilter、AuthenticationEntryPoint、AuthenticationProvider以及Acegi的認(rèn)證機(jī)制是其中的代表者。
ExceptionTranslationFilter是一個(gè)Acegi的Servlet過(guò)濾器,它負(fù)責(zé)探測(cè)拋出的安全異常。當(dāng)一個(gè)未認(rèn)證用戶訪問(wèn)服務(wù)器 時(shí),Acegi將引發(fā)一個(gè)Java異常。Java異常本身對(duì)HTTP請(qǐng)求以及如何認(rèn)證用戶是一無(wú)所知的, ExceptionTranslationFilter適時(shí)登場(chǎng),對(duì)這個(gè)異常進(jìn)行處理,啟動(dòng)用戶認(rèn)證的步驟(第3步)。如果已認(rèn)證用戶越權(quán)訪問(wèn)一個(gè)資源, Acegi也將引發(fā)一個(gè)Java異常,ExceptionTranslationFilter則將這個(gè)異常轉(zhuǎn)換為HTTP 403響應(yīng)碼(第7步)。可見(jiàn),Acegi通過(guò)異常進(jìn)行通訊,
ExceptionTranslationFilter接收這些異常并作出相應(yīng)的動(dòng)作。
當(dāng)ExceptionTranslationFilter通過(guò)Java異常發(fā)現(xiàn)用戶還未認(rèn)證時(shí),它到底會(huì)將請(qǐng)求重定向哪個(gè)頁(yè)面以要求用戶提供認(rèn)證信息呢? 這通過(guò)咨詢AuthenticationEntryPoint來(lái)達(dá)到目的——Acegi通過(guò)AuthenticationEntryPoint描述登錄頁(yè) 面。
當(dāng)你的瀏覽器通過(guò)HTTP表單或HTTP報(bào)文頭向服務(wù)器提供用戶認(rèn)證信息時(shí),Acegi需要將這些信息收集到Authentication中,Acegi 用“認(rèn)證機(jī)制”描述這一過(guò)程。此時(shí),這個(gè)新生成Authentication只包含用戶提供的認(rèn)證信息,但并未通過(guò)認(rèn)證。
AuthenticationProvider 負(fù)責(zé)對(duì)Authentication進(jìn)行認(rèn)證。AuthenticationProvider究竟如何完成這一過(guò)程呢?請(qǐng)回憶一下上節(jié)我們所介紹的 UserDetails和UserDetailsService,大多數(shù)AuthenticationProvider通過(guò) UserDetailsService獲取和未認(rèn)證的Authentication對(duì)應(yīng)的UserDetails并進(jìn)行匹配比較來(lái)完成這一任務(wù)。當(dāng)用戶認(rèn) 證信息匹配時(shí),Authentication被認(rèn)為是有效的,AuthenticationProvider進(jìn)一步將UserDetails中權(quán)限、 ACL等信息拷貝到Authentication。
當(dāng)Acegi通過(guò)認(rèn)證機(jī)制收集到用戶認(rèn)證信息并填充好Authentication后,Authentication將被保存到SecurityContextHolder中并處理用戶的原始請(qǐng)求(第7步)。
你完全可以拋開(kāi)Acegi的安全機(jī)制,編寫(xiě)自己的Servlet過(guò)濾器,使用自己的方案構(gòu)建Authentication對(duì)象并將其放置到SecurityContextHolder中。也許你使用了CMA(Container
Managed Authentication:容器管理認(rèn)證),CMA允許你從ThreadLocal或JNDI中獲取用戶認(rèn)證信息,這時(shí)你只要獲取這些信息并將其轉(zhuǎn)換為Authentication就可以了。
安全對(duì)象訪問(wèn)控制
Acegi稱受保護(hù)的應(yīng)用資源為“安全對(duì)象”,這包括URL資源和業(yè)務(wù)類方法。我們知道在Spring AOP中有前置增強(qiáng)、后置增強(qiáng)、異常增強(qiáng)和環(huán)繞增強(qiáng),其中環(huán)繞增強(qiáng)的功能最為強(qiáng)大——它不但可以在目標(biāo)方法被訪問(wèn)前攔截調(diào)用,還可以在調(diào)用返回前改變返回 的結(jié)果,甚至拋出異常。Acegi使用環(huán)繞增強(qiáng)對(duì)安全對(duì)象進(jìn)行保護(hù)。
Acegi通過(guò)AbstractSecurityInterceptor為安全對(duì)象訪問(wèn)提供一致的工作模型,它按照以下流程進(jìn)行工作:
1. 從SecurityContext中取出已經(jīng)認(rèn)證過(guò)的Authentication(包括權(quán)限信息);
2. 通過(guò)反射機(jī)制,根據(jù)目標(biāo)安全對(duì)象和“配置屬性”得到訪問(wèn)目標(biāo)安全對(duì)象所需的權(quán)限;
3. AccessDecisionManager根據(jù)Authentication的授權(quán)信息和目標(biāo)安全對(duì)象所需權(quán)限做出是否有權(quán)訪問(wèn)的判斷。如果無(wú)權(quán)訪問(wèn),Acegi將拋出AccessDeniedException異常,否則到下一步;
4. 訪問(wèn)安全對(duì)象并獲取結(jié)果(返回值或HTTP響應(yīng));
5. AbstractSecurityInterceptor可以在結(jié)果返回前進(jìn)行處理:更改結(jié)果或拋出異常。 Acegi稱受保護(hù)的應(yīng)用資源為“安全對(duì)象”,這包括URL資源和業(yè)務(wù)類方法。我們知道在Spring AOP中有前置增強(qiáng)、后置增強(qiáng)、異常增強(qiáng)和環(huán)繞增強(qiáng),其中環(huán)繞增強(qiáng)的功能最為強(qiáng)大——它不但可以在目標(biāo)方法被訪問(wèn)前攔截調(diào)用,還可以在調(diào)用返回前改變返回 的結(jié)果,甚至拋出異常。Acegi使用環(huán)繞增強(qiáng)對(duì)安全對(duì)象進(jìn)行保護(hù)。 Acegi通過(guò)AbstractSecurityInterceptor為安全對(duì)象訪問(wèn)提供一致的工作模型,它按照以下流程進(jìn)行工作: 1. 從SecurityContext中取出已經(jīng)認(rèn)證過(guò)的Authentication(包括權(quán)限信息); 2. 通過(guò)反射機(jī)制,根據(jù)目標(biāo)安全對(duì)象和“配置屬性”得到訪問(wèn)目標(biāo)安全對(duì)象所需的權(quán)限; 3. AccessDecisionManager根據(jù)Authentication的授權(quán)信息和目標(biāo)安全對(duì)象所需權(quán)限做出是否有權(quán)訪問(wèn)的判斷。如果無(wú)權(quán)訪問(wèn), Acegi將拋出AccessDeniedException異常,否則到下一步; 4. 訪問(wèn)安全對(duì)象并獲取結(jié)果(返回值或HTTP響應(yīng)); 5. AbstractSecurityInterceptor可以在結(jié)果返回前進(jìn)行處理:更改結(jié)果或拋出異常。

圖 6 AbstractSecurityInterceptor工作流程
安全對(duì)象和一般對(duì)象的區(qū)別在于前者通過(guò)Acegi的“配置屬性”進(jìn)行了描述,如“/view.jsp=PRIV_COMMON”配置屬性就將 “/view.jsp”這個(gè)URL資源標(biāo)識(shí)為安全對(duì)象,它表示用戶在訪問(wèn)/view.jsp時(shí),必須擁有PRIV_COMMON這個(gè)權(quán)限。配置屬性通過(guò) XML配置文件,注解、數(shù)據(jù)庫(kù)等方式提供。安全對(duì)象通過(guò)配置屬性表示為一個(gè)權(quán)限,這樣,Acegi就可以根據(jù)Authentication的權(quán)限信息獲知 用戶可以訪問(wèn)的哪些安全對(duì)象。
根據(jù)安全對(duì)象的性質(zhì)以及具體實(shí)現(xiàn)技術(shù),AbstractSecurityInterceptor擁有以下三個(gè)實(shí)現(xiàn)類:
? FilterSecurityInterceptor:對(duì)URL資源的安全對(duì)象進(jìn)行調(diào)用時(shí),通過(guò)該攔截器實(shí)施環(huán)繞切面。該攔截器使用Servlet過(guò)濾器實(shí)現(xiàn)AOP切面,它本身就是一個(gè)Servlet過(guò)濾器;
? MethodSecurityInterceptor:當(dāng)調(diào)用業(yè)務(wù)類方法的安全對(duì)象時(shí),可通過(guò)該攔截器類實(shí)施環(huán)繞切面;
? AspectJSecurityInterceptor:和MethodSecurityInterceptor類似,它是針對(duì)業(yè)務(wù)類方法的攔截器,只不過(guò)它通過(guò)AspectJ實(shí)施AOP切面。
Acegi版本升級(jí)的一些重大變化
Acegi項(xiàng)目開(kāi)始于2003年,Acegi團(tuán)隊(duì)在發(fā)布新版本時(shí)非常謹(jǐn)慎,在本書(shū)寫(xiě)作之時(shí),Acegi最新版本為1.0.3。在此之前Acegi已經(jīng)發(fā)布 了10多個(gè)預(yù)覽版本,由于Acegi框架優(yōu)異的表現(xiàn),許多大型應(yīng)用早在Acegi 1.0正式版本發(fā)布之前(2006年5月),就已經(jīng)采用Acegi框架作為其安全訪問(wèn)控制的解決方案。
在Acegi社區(qū)里,來(lái)自世界各地眾多優(yōu)秀的安全領(lǐng)域?qū)<覍?duì)Acegi的改進(jìn)和發(fā)展獻(xiàn)計(jì)獻(xiàn)策,Acegi團(tuán)隊(duì)廣泛聽(tīng)取并吸收各種有益的建議,將它們?nèi)谌氲紸cegi的框架中,使Acegi成為構(gòu)建在Spring基礎(chǔ)上企業(yè)應(yīng)用的首選安全控制框架。
Acegi 1.0.3版本相比于早期預(yù)覽版本發(fā)生了很大的變化,對(duì)于需要進(jìn)行Acegi版本的項(xiàng)目來(lái)說(shuō),了解這一變化特別重要。下面,我們列出Acegi的一些重大的升級(jí)更新:
? 包名的更新:在0.9.0及之前的版本中,Acegi采用net.sf.acegisecurity包名前綴,在1.0.0版本之后更改為 org.acegisecurity(Hibernate也走過(guò)相同的道路,好在Acegi在正式版本發(fā)布之時(shí)就完成了這種轉(zhuǎn)變);
? ACL模塊的調(diào)整:ACL模塊發(fā)生了重大的調(diào)整,Acegi團(tuán)隊(duì)接收了社區(qū)大量關(guān)于ACL模塊的反饋意見(jiàn),重新設(shè)計(jì)了ACL模塊的底層結(jié)構(gòu),在性能、封裝 性、靈活性上得到了質(zhì)的提升。事實(shí)上,Acegi使用org.acegisecurity.acls包代替了原來(lái)的 org.acegisecurity.acl包,后者將在后期的版本中刪除,由于這種傷筋動(dòng)骨的變化,將很難兼容原來(lái)ACL模塊。不過(guò),目前基于新框架的 ACL模塊還沒(méi)有進(jìn)行充分的測(cè)試,Acegi承諾在1.1.0版本發(fā)布時(shí)提供最終的實(shí)現(xiàn);
? 刪除了ContextHolder及其相關(guān)類:在Acegi 0.9版本中,ContextHolder及其相關(guān)類被徹底從Acegi項(xiàng)目中刪除。ContextHolder可以在多個(gè)HTTP請(qǐng)求中共享同一個(gè) ThreadLocal,這和Spring提倡的ThreadLocal只應(yīng)在同一線程中共享相?!,F(xiàn)在,Acegi使用 SecurityContextHolder替換ContextHolder,它的生命周期是一個(gè)HTTP 請(qǐng)求;
? 使用FilterChainProxy同時(shí)代理多個(gè)過(guò)濾器:在早期的版本中,Acegi通過(guò)FilterToBeanProxy將web.xml中的 Servlet過(guò)濾器定義轉(zhuǎn)移到Spring容器中。這比直接在web.xml中配置Servlet過(guò)濾器要方便一些,但是Acegi框架往往需要定義多 個(gè)Servlet過(guò)濾器,使web.xml配置文件變得冗長(zhǎng)難看。在Acegi 0.8版本中提供FilterChainProxy,它可以同時(shí)代理多個(gè)Servlet過(guò)濾器并保證過(guò)濾器的順序。因此在新版本中, FilterChainProxy成為推薦的選擇。
小結(jié)
Acegi是Spring項(xiàng)目下一個(gè)成熟的安全訪問(wèn)控制框架,它允許利用了Spring IoC的AOP的功能完成安全對(duì)象的訪問(wèn)控制。在Acegi框架中,SecurityContextHolder處于非常核心的位置,它是存放認(rèn)證管理器 用戶安全信息SecurityContext的“容器”,SecurityContext保存著用戶安全訪問(wèn)控制所需的信息,直接被訪問(wèn)決策管理器使用。 HttpSessionContextIntegrationFilter通過(guò)在SecurityContextHolder和HttpSession中 擺渡SecurityContext,使多個(gè)請(qǐng)求線程可以共享同一個(gè)SecurityContext。
凡是有該標(biāo)志的文章,都是該blog博主Caoer(草兒)原創(chuàng),凡是索引、收藏
、轉(zhuǎn)載請(qǐng)注明來(lái)處和原文作者。非常感謝。