Acegi框架介紹

    概述
    對於任何一個完整的應用系 統,完善的認證和受權機制是必不可少的。Acegi Security(如下簡稱Acegi)是一個能爲基於Spring的企業應用提供強大而靈活安全訪問控制解決方案的框架,Acegi已經成爲 Spring官方的一個子項目,因此也稱爲Spring Security。它經過在Spring容器中配置一組Bean,充分利用Spring的IoC和AOP功能,提供聲明式安全訪問控制的功能。雖然,如今 Acegi也能夠應用到非Spring的應用程序中,但在Spring中使用Acegi是最天然的方式。
Acegi能夠實現業務對象方法級的安 全訪問控制粒度,它提供瞭如下三方面的應用程序的安全:

? URL資源的訪問控制
    如全部用戶(包括其名用戶)能夠訪問index.jsp登陸頁面,而只有受權的用戶能夠訪問/user/addUser.jsp頁面。Acegi容許經過 正則表達式或Ant風格的路徑表達式定義URL模式,讓受權用戶訪問某一URL匹配模式下的對應URL資源。

? 業務 類方法的訪問控制
    Spring容器中全部Bean的方法均可以被Acegi管理,如全部用戶能夠調用BbtForum#getRefinedTopicCount()方 法,而只有受權用戶能夠調用BbtForum#addTopic()方法。

? 領域對象的訪問控制
    業務類方法表明一個具體的業務操做,好比更改、刪除、審批等,業務類方法訪問控制解決了用戶是否有調用某種操做的權限,但並未對操做的客體(領域對象)進 行控制。對於咱們的論壇應用來講,用戶能夠調用BbtForum#updateUser(User user)方法更改用戶註冊信息,但應該僅限於更改本身的用戶信息,也即調用BbtForum#updateUser()所操做的User這個領域對象必 須是受限的。

    Acegi經過多個不一樣用途的Servlet過濾器對URL資源進行保護,在請求受保護的URL資源前,Acegi的Servlet過濾器判斷用戶是否有 權訪問目標資源,受權者被開放訪問,而未未被受權者將被阻擋在大門以外。
    Acegi經過Spring AOP對容器中Bean的受控方法進行攔截,當用戶的請求引起調用Bean的受控方法時,Acegi的方法攔截器開始工做,阻止未受權者的調用。 
    
    對領域對象的訪問控制創建在對Bean方法保護的基礎上,在最終開放目標Bean方法的執行前,Acegi將檢查用戶的ACL(Aeccess Control List:訪問控制列表)是否包含正要進行操做的領域對象,只有領域對象被受權時,用戶纔可使用Bean方法對領域對象進行處理。此外,Acegi還可 以對Bean方法返回的結果進行過濾,將一些不在當前用戶訪問權限範圍內的領域對象剔除掉——即傳統的數據可視域範圍的控制。通常來講,使用Acegi控 制數據可視域未非理想的選擇,相反經過傳統的動態SQL的解決方案每每更加簡單易行。

    從本質特性上來講,Servlet過濾器就是最原始的原生態AOP,因此咱們能夠說Acegi不但對業務類方法、領域對象訪問控制採用了AOP技術方案, 對URL資源的訪問控制也使用了AOP的技術方案。使用AOP技術方案的框架是使人振奮的,這意味着,開發者能夠在應用程序業務功能開發完畢後,輕鬆地通 過Acegi給應用程序穿上安全保護的「鐵布衫」。程序員

    Acegi體系結構
    乘飛機前須要經過安檢,乘客必須提供身份證以驗證其身份。在經過安檢進入候機室後,國航、海航、南航等不一樣航空公司的飛機陸續到達,但你只能登上機票上對 應航班的飛機。在登機後,只能坐在機票對應的座位上——你不能搶佔他人的座位,你不能在座位上刻字留念、你不能要求空姐打開機窗……

    乘飛機的過程最能體現安全控制的流程,咱們能夠從中找到身份認證、資源訪問控制、領域對象安全控制的對應物:安檢對應身份認證,登機對應資源訪問控制而按 號就座則對應領域對象安全控制。
    Acegi經過兩個組件對象完成以上安全問題的處理:AuthenticationManager(認證管理器)、 AccessDecisionManager(訪問控制管理器),如圖 1所示:web


圖 1 Acegi體系結構正則表達式

    SecurityContextHolder是框架級的容器,它保存着和全部用戶關聯SecurityContext實 例,SecurityContext承載着用戶(也稱認證主體)的身份信息的權限信息, AuthenticationManager、AccessDecisionManager將據此進行安全訪問控制。
   
    SecurityContext的認證主體安全信息在一個HTTP請求線程的多個調用之間是共享的(經過ThreadLocal),但它不能在多個請求之 間保持共享。爲了解決這個問題,Acegi將認證主體安全信息緩存於HttpSession中,當用戶請求一個受限的資源時,Acegi經過 HttpSessionContextIntegrationFilter將認證主體信息從HttpSession中加載到 SecurityContext實例中,認證主體關聯的SecurityContext實例保存在Acegi容器級的 SecurityContextHolder裏。當請求結束以後,HttpSessionContextIntegrationFilter執行相反的操 做,將SecurityContext中的認證主體安全信息從新轉存到HttpSession中,而後從SecurityContextHolder中清 除對應的SecurityContext實例。經過HttpSession轉存機制,用戶的安全信息就能夠在多個HTTP請求間共享,同時保證 SecurityContextHolder中僅保存當前有用的用戶安全信息,其總體過程如圖 2所示:數據庫


圖 2 SecurityContext在HttpSession和請求線程間的轉交過程瀏覽器


    當用戶請求一個受限的資源時,AuthenticationManager首先開始工做,它象一個安檢入口,對用戶身份進行覈查,用戶必須提供身份認證的 憑證(通常是用戶名/密碼)。在進行身份認證時,AuthenticationManager將身份認證的工做委託給多個 AuthenticationProvider。由於在具體的系統中,用戶身份可能存儲在不一樣的用戶信息安全系統中(如數據庫、CA中心、LDAP服務 器),不一樣用戶信息安全系統須要不一樣的AuthenticationProvider執行諸如用戶信息查詢、用戶身份判斷、用戶受權信息獲取等工做。只要 有一個AuthenticationProvider能夠識別用戶的身份,AuthenticationManager就經過用戶身份認證,並將用戶的授 權信息放入到SecurityContext中。

   當用戶經過身份認證後,試圖訪問某個受限的程序資源時,AccessDecisionManager開始工做。 AccessDecisionManager採用民主決策機制判斷用戶是否有權訪問目標程序資源,它包含了多個AccessDecisionVoter。 在訪問決策時每一個AccessDecisionVoter都擁有投票權,AccessDecisionManager統計投票結果,並按照某種決策方式根 據這些投票結果決定最終是否向用戶開放受限資源的訪問。緩存

    重要組件類介紹
    每一個框架都有一些核心的概念,這些概念被固化爲類和接口,成爲框架的重要組件類。框架的管理類、操做類都在這些組件類的基礎上進行操做。在進入Acegi 框架的具體學習前,有必要事先了解一下這些承載Acegi框架重要概念的組件類。
    首先,咱們要接觸是UserDetails接口,它表明一個應用系統的用戶,該接口定義了用戶安全相關的信息,如用戶名/密碼,用戶是否有效等信息,你可 以根據如下接口方法進行相關信息的獲取:
    String getUsername():獲取用戶名; 
     String getPassword():獲取密碼; 
     boolean isAccountNonExpired():用戶賬號是否過時; 
     boolean isAccountNonLocked():用戶賬號是否鎖定; 
     boolean isCredentialsNonExpired():用戶的憑證是否過時; 
     boolean isEnabled():用戶是否處於激活狀態。
    當以上任何一個判斷用戶狀態的方法都返回false時,用戶憑證就被視爲無效。
    UserDetails還定義了獲取用戶權限信息的方法:GrantedAuthority[] getAuthorities(),GrantedAuthority表明用戶權限信息,它定義了一個獲取權限描述信息(以字符串表示,如 PRIV_COMMON)的方法:String getAuthority()。安全


圖 3 用戶和權限服務器

    在未使用Acegi以前,咱們可能經過相似User、Customer等領域對象表示用戶的概念,並在程序中編寫相應的用戶認證的邏輯。如今,你要作的一 個調整是讓原先這些表明用戶概念的領域類實現UserDetails接口,這樣,Acegi就能夠經過UserDetails接口訪問到用戶的信息了。 

    UserDetails可能從數據庫、LDAP等用戶信息資源中返回,這要求有一種機制來完成這項工做,UserDetailsService正是充當這 一角色的接口。UserDetailsService接口很簡單,僅有一個方法:UserDetails loadUserByUsername(String username) ,這個方法經過用戶名獲取整個UserDetails對象。
Authentication 表明一個和應用程序交互的待認證用戶,Acegi從相似於登陸頁面、Cookie等處獲取待認證的用戶信息(通常是用戶名密碼)自動構造 Authentication實例。併發


圖 4 Acegi的認證用戶框架

    Authentication能夠經過Object getPrincipal()獲取一個表明用戶的對象,這個對象通常能夠轉換爲UserDetails,從中能夠取得用戶名/密碼等信息。在 Authentication被AuthenticationManager認證以前,沒有任何權限的信息。在經過認證以後,Acegi經過 UserDetails將用戶對應的權限信息加載到Authentication中。Authentication擁有一個 GrantedAuthority[] getAuthorities()方法,經過該方法能夠獲得用戶對應的權限信息。
    Authentication和UserDetails很容易被混淆,由於二者都有用戶名/密碼及權限的信息,接口方法也很相似。其實 Authentication是Acegi進行安全訪問控制真正使用的用戶安全信息的對象,它擁有兩個狀態:未認證和已認證。UserDetails是代 表一個從用戶安全信息源(數據庫、LDAP服務器、CA中心)返回的真正用戶,Acegi須要將未認證的Authentication和表明真實用戶的 UserDetails進行匹配比較,經過匹配比較(簡單的狀況下是用戶名/密碼是否一致)後,Acegi將UserDetails中的其它安全信息(如 權限、ACL等)拷貝到Authentication中。這樣,Acegi安全控制組件在後續的安全訪問控制中只和Authentication進行交 互。

    因爲Acegi對程序資源進行訪問安全控制時,必定要事先獲取和請求用戶對應的Authentication,Acegi框架必須爲 Authentication提供一個「寓所」,以便在須要時直接從「寓所」把它請出來,做爲各類安全管理器決策的依據。

    SecurityContextHolder就是Authentication容身的「寓所」,你能夠經過 SecurityContextHolder.getContext().getAuthenication()代碼獲取Authentication。 細心觀察一下這句代碼,你會發如今SecurityContextHolder和Authentication之間存在一個getContext()中 介,這個方法返回SecurityContext對象。SecurityContext這個半路殺出來的程咬金有什麼特殊的用途呢?咱們知道 Authentication是用戶安全相關的信息,請求線程其它信息(如登陸驗證碼等)則放置在SecurityContext中,構成了一個完整的安 全信息上下文。SecurityContext接口提供了獲取和設置Authentication的方法:
 Authentication getAuthentication()
 void setAuthentication(Authentication authentication)


圖 5 認證用戶信息存儲器

    SecurityContextHolder是Acegi框架級的對象,它在內部經過ThreadLocal爲請求線程提供線程綁定的 SecurityContext對象。這樣,任何參與當前請求線程的Acegi安全管理組件、業務服務對象等均可以直接經過 SecurityContextHolder.getContext()獲取線程綁定的SecurityContext,避免經過方法入參的方式獲取用戶 相關的SecurityContext。

    線程綁定模式對於大多數應用來講是適合的,可是應用自己會建立其它的線程,那麼只有主線程能夠得到線程綁定SecurityContext,而主線程衍生 出的新線程則沒法獲得線程綁定的SecurityContext。Acegi考慮到了這些不一樣應用狀況,提供了三種綁定SecurityContext的 模式:
 SecurityContextHolder.MODE_THREADLOCAL:SecurityContext綁定到主線程,這是默認的模式;
 SecurityContextHolder.MODE_GLOBAL:SecurityContext綁定到JVM中,全部線程都使用同一個 SecurityContext;
 SecurityContextHolder.MODE_INHERITABLETHREADLOCAL::SecurityContext綁定到主線程 及由主線程衍生的線程中。
    你能夠經過SecurityContextHolder.setStrategyName(String strategyName)方法指定SecurityContext的綁定模式。

    用戶認證過程
Acegi支持多種方式的用戶認證:如典型的基於數據庫的認證、基於LDAP的認 證、基於Yale中心認證等方式。不一樣的認證環境擁有不一樣的用戶認證方式,如今咱們先拋開這些具體的細節,考察一下Acegi對受限資源進行訪問控制的典 型過程:
    1.你點擊一個連接訪問一個網頁;
    2.瀏覽器發送一個請求到服務器,服務器判斷出你正在訪問一個受保護的資源;
    3.若是此時你並未經過身份認證,服務器發回一個響應提示你進行認證——這個響應多是一個HTTP響應代碼,抑或重定向到一個指定頁面;
    4.根據系統使用認證機制的不一樣,瀏覽器或者重定向到一個登陸頁面中,或者由瀏覽器經過一些其它的方式獲取你的身份信息(如經過BASIC認證對話框、一 個Cookie或一個X509證書);
    5.瀏覽器再次將用戶身份信息發送到服務器上(多是一個用戶登陸表單的HTTP POST信息、也多是包含認證信息的HTTP報文頭);
    6.服務器判斷用戶認證信息是否有效,若是無效,通常狀況下,瀏覽器會要求你繼續嘗試,這意味着返回第3步。若是有效,則到達下一步;
    7.服務器從新響應第2步所提交的原始請求,並判斷該請求所訪問的程序資源是否在你的權限範圍內,若是你有權訪問,請求將獲得正確的執行並返回結果。否 則,你將收到一個HTTP 403錯誤,這意味着你被禁止訪問。
    在Acegi框架裏,你能夠找到對應以上大多數步驟的類,其中ExceptionTranslationFilter、 AuthenticationEntryPoint、AuthenticationProvider以及Acegi的認證機制是其中的表明者。

    ExceptionTranslationFilter是一個Acegi的Servlet過濾器,它負責探測拋出的安全異常。當一個未認證用戶訪問服務器 時,Acegi將引起一個Java異常。Java異常自己對HTTP請求以及如何認證用戶是一無所知 的,ExceptionTranslationFilter適時登場,對這個異常進行處理,啓動用戶認證的步驟(第3步)。若是已認證用戶越權訪問一個資 源,Acegi也將引起一個Java異常,ExceptionTranslationFilter則將這個異常轉換爲HTTP 403響應碼(第7步)。可見,Acegi經過異常進行通信,
ExceptionTranslationFilter接收這些異常並做出相應的動 做。

    當ExceptionTranslationFilter經過Java異常發現用戶還未認證時,它到底會將請求重定向哪一個頁面以要求用戶提供認證信息呢? 這經過諮詢AuthenticationEntryPoint來達到目的——Acegi經過AuthenticationEntryPoint描述登陸頁 面。

    當你的瀏覽器經過HTTP表單或HTTP報文頭向服務器提供用戶認證信息時,Acegi須要將這些信息收集到Authentication中,Acegi 用「認證機制」描述這一過程。此時,這個新生成Authentication只包含用戶提供的認證信息,但並未經過認證。
AuthenticationProvider 負責對Authentication進行認證。AuthenticationProvider究竟如何完成這一過程呢?請回憶一下上節咱們所介紹的 UserDetails和UserDetailsService,大多數AuthenticationProvider經過 UserDetailsService獲取和未認證的Authentication對應的UserDetails並進行匹配比較來完成這一任務。當用戶認 證信息匹配時,Authentication被認爲是有效的,AuthenticationProvider進一步將UserDetails中權限、 ACL等信息拷貝到Authentication。
當Acegi經過認證機制收集到用戶認證信息並填充好Authentication 後,Authentication將被保存到SecurityContextHolder中並處理用戶的原始請求(第7步)。

    你徹底能夠拋開Acegi的安全機制,編寫本身的Servlet過濾器,使用本身的方案構建Authentication對象並將其放置到 SecurityContextHolder中。也許你使用了CMA(Container
Managed Authentication:容器管理認證),CMA容許你從ThreadLocal或JNDI中獲取用戶認證信息,這時你只要獲取這些信息並將其轉換 爲Authentication就能夠了。

    安全對象訪問控制
    Acegi稱受保護的應用資源爲「安全對象」,這包括URL資源和業務類方法。咱們知道在Spring AOP中有前置加強、後置加強、異常加強和環繞加強,其中環繞加強的功能最爲強大——它不但能夠在目標方法被訪問前攔截調用,還能夠在調用返回前改變返回 的結果,甚至拋出異常。Acegi使用環繞加強對安全對象進行保護。
    Acegi經過AbstractSecurityInterceptor爲安全對象訪問提供一致的工做模型,它按照如下流程進行工做:
    1. 從SecurityContext中取出已經認證過的Authentication(包括權限信息);
    2. 經過反射機制,根據目標安全對象和「配置屬性」獲得訪問目標安全對象所需的權限;
    3. AccessDecisionManager根據Authentication的受權信息和目標安全對象所需權限作出是否有權訪問的判斷。若是無權訪 問,Acegi將拋出AccessDeniedException異常,不然到下一步;
4. 訪問安全對象並獲取結果(返回值或HTTP響應);
5. AbstractSecurityInterceptor能夠在結果返回前進行處理:更改結果或拋出異常。     Acegi稱受保護的應用資源爲「安全對象」,這包括URL資源和業務類方法。咱們知道在Spring AOP中有前置加強、後置加強、異常加強和環繞加強,其中環繞加強的功能最爲強大——它不但能夠在目標方法被訪問前攔截調用,還能夠在調用返回前改變返回 的結果,甚至拋出異常。Acegi使用環繞加強對安全對象進行保護。     Acegi經過AbstractSecurityInterceptor爲安全對象訪問提供一致的工做模型,它按照如下流程進行工做:     1. 從SecurityContext中取出已經認證過的Authentication(包括權限信息);     2. 經過反射機制,根據目標安全對象和「配置屬性」獲得訪問目標安全對象所需的權限;     3. AccessDecisionManager根據Authentication的受權信息和目標安全對象所需權限作出是否有權訪問的判斷。若是無權訪 問,Acegi將拋出AccessDeniedException異常,不然到下一步; 4. 訪問安全對象並獲取結果(返回值或HTTP響應); 5. AbstractSecurityInterceptor能夠在結果返回前進行處理:更改結果或拋出異常。


圖 6 AbstractSecurityInterceptor工做流程

    安全對象和通常對象的區別在於前者經過Acegi的「配置屬性」進行了描述,如「/view.jsp=PRIV_COMMON」配置屬性就將「 /view.jsp」這個URL資源標識爲安全對象,它表示用戶在訪問/view.jsp時,必須擁有PRIV_COMMON這個權限。配置屬性經過 XML配置文件,註解、數據庫等方式提供。安全對象經過配置屬性表示爲一個權限,這樣,Acegi就能夠根據Authentication的權限信息獲知 用戶能夠訪問的哪些安全對象。
    根據安全對象的性質以及具體實現技術,AbstractSecurityInterceptor擁有如下三個實現類:
 FilterSecurityInterceptor:對URL資源的安全對象進行調用時,經過該攔截器實施環繞切面。該攔截器使用Servlet過濾器 實現AOP切面,它自己就是一個Servlet過濾器;
 MethodSecurityInterceptor:當調用業務類方法的安全對象時,可經過該攔截器類實施環繞切面;
 AspectJSecurityInterceptor:和MethodSecurityInterceptor相似,它是針對業務類方法的攔截器,只不 過它經過AspectJ實施AOP切面。

    Acegi版本升級的一些重大變化 
    Acegi項目開始於2003年,Acegi團隊在發佈新版本時很是謹慎,在本書寫做之時,Acegi最新版本爲1.0.3。在此以前Acegi已經發布 了10多個預覽版本,因爲Acegi框架優異的表現,許多大型應用早在Acegi 1.0正式版本發佈以前(2006年5月),就已經採用Acegi框架做爲其安全訪問控制的解決方案。

    在Acegi社區裏,來自世界各地衆多優秀的安全領域專家對Acegi的改進和發展獻計獻策,Acegi團隊普遍聽取並吸取各類有益的建議,將它們融入到 Acegi的框架中,使Acegi成爲構建在Spring基礎上企業應用的首選安全控制框架。
Acegi 1.0.3版本相比於早期預覽版本發生了很大的變化,對於須要進行Acegi版本的項目來講,瞭解這一變化特別重要。下面,咱們列出Acegi的一些重大 的升級更新:
 包名的更新:在0.9.0及以前的版本中,Acegi採用net.sf.acegisecurity包名前綴,在1.0.0版本以後更改成 org.acegisecurity(Hibernate也走過相同的道路,好在Acegi在正式版本發佈之時就完成了這種轉變);

 ACL模塊的調整:ACL模塊發生了重大的調整,Acegi團隊接收了社區大量關於ACL模塊的反饋意見,從新設計了ACL模塊的底層結構,在性能、封裝 性、靈活性上獲得了質的提高。事實上,Acegi使用org.acegisecurity.acls包代替了原來的 org.acegisecurity.acl包,後者將在後期的版本中刪除,因爲這種傷筋動骨的變化,將很難兼容原來ACL模塊。不過,目前基於新框架的 ACL模塊尚未進行充分的測試,Acegi承諾在1.1.0版本發佈時提供最終的實現;

 刪除了ContextHolder及其相關類:在Acegi 0.9版本中,ContextHolder及其相關類被完全從Acegi項目中刪除。ContextHolder能夠在多個HTTP請求中共享同一個 ThreadLocal,這和Spring提倡的ThreadLocal只應在同一線程中共享相悖。如今,Acegi使用 SecurityContextHolder替換ContextHolder,它的生命週期是一個HTTP 請求;

 使用FilterChainProxy同時代理多個過濾器:在早期的版本中,Acegi經過FilterToBeanProxy將web.xml中的 Servlet過濾器定義轉移到Spring容器中。這比直接在web.xml中配置Servlet過濾器要方便一些,可是Acegi框架每每須要定義多 個Servlet過濾器,使web.xml配置文件變得冗長難看。在Acegi 0.8版本中提供FilterChainProxy,它能夠同時代理多個Servlet過濾器並保證過濾器的順序。所以在新版本 中,FilterChainProxy成爲推薦的選擇。

    小結
    Acegi是Spring項目下一個成熟的安全訪問控制框架,它容許利用了Spring IoC的AOP的功能完成安全對象的訪問控制。在Acegi框架中,SecurityContextHolder處於很是核心的位置,它是存放認證管理器 用戶安全信息SecurityContext的「容器」,SecurityContext保存着用戶安全訪問控制所需的信息,直接被訪問決策管理器使用。 HttpSessionContextIntegrationFilter經過在SecurityContextHolder和HttpSession中 擺渡SecurityContext,使多個請求線程能夠共享同一個SecurityContext。

原文地址:https://www.oschina.net/question/12_8396
相關文章
相關標籤/搜索