無為

          無為則可為,無為則至深!

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            190 Posts :: 291 Stories :: 258 Comments :: 0 Trackbacks
          【IT168 專稿】對于任何一個完整的應用系統,完善的 認證和授權機制是必不可少的。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所示:


          圖 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。


          凡是有該標志的文章,都是該blog博主Caoer(草兒)原創,凡是索引、收藏
          、轉載請注明來處和原文作者。非常感謝。

          posted on 2007-12-16 21:29 草兒 閱讀(1570) 評論(0)  編輯  收藏 所屬分類: JAVA WEB應用
          主站蜘蛛池模板: 宁陵县| 南木林县| 怀宁县| 英德市| 攀枝花市| 定安县| 雷山县| 桓台县| 五峰| 建瓯市| 拉孜县| 望奎县| 泰安市| 涪陵区| 绥中县| 柳林县| 鄢陵县| 将乐县| 嘉鱼县| 保康县| 图们市| 砀山县| 南康市| 义乌市| 北川| 迭部县| 大冶市| 奉化市| 东海县| 洛南县| 海阳市| 融水| 嵊州市| 美姑县| 建宁县| 来宾市| 旅游| 顺平县| 凤冈县| 南涧| 双辽市|