《Spring Security3》第五章第三部分翻譯(保護業務層)

保護業務層

         到目前爲止,在本書中我們的關注點都主要在JBCP Pets 應用web層面的安全。但是,在實際的安全系統規劃中,對服務方法應該給予同等的重視,因爲它們能夠訪問系統中最重要的部分——數據。

         Spring Security支持添加授權層(或者基於授權的數據處理)到應用中所有Spring管理的bean中。儘管很多的開發人員關注層的安全,其實業務層的俄安全同等主要,因爲惡意的用戶可能會穿透web層,能夠通過沒有UI的前端訪問暴露的服務,如使用web service。

 

         讓我們查看下面的圖以瞭解我們將要添加安全層的位置:



 Spring Security有兩個主要技術以實現方法的安全:

l 事先授權(Pre-authorization)保證在執行一個方法之前需要滿足特定的要求——例如,一個用戶要擁有特定的GrantedAuthority,如ROLE_ADMIN。不能滿足聲明的條件將會導致方法調用失敗;

l事後授權(Post-authorization)保證在方法返回時,調用的安全實體滿足聲明的條件。這很少被使用,但是能夠在一些複雜交互的業務方法周圍提供額外的安全層。        

          事先和事後授權在面向對象設計中提供了所謂的前置條件和後置條件(preconditions and postconditions)。前置條件和後置條件允許開發者聲明運行時的檢查,從而保證在一個方法執行時特定的條件需要滿足。在安全的事前授權和事後授權中,業務層的開發人員需要對特定的方法確定明確的安全信息,並在接口或類的API聲明中添加期望的運行時條件。正如你可能想象的那樣,這需要大量的規劃以避免不必要的影響。

         

保護業務層方法的基本知識

         讓我們以JBCP Pets中業務層的幾個方法爲例闡述怎樣爲它們應用典型的規則。

         我們對JBCP Pets的基礎代碼進行了重新組織以實現三層的設計,作爲修改的一部分我們抽象出了前面章節已經介紹到的修改密碼功能到業務層。不同於用web MVC的控制器直接訪問JDBC DAO,我們選擇插入一個業務服務以提供要求的附加功能。下圖對此進行了描述:


 

我們能夠看到在例子中com.packtpub.springsecurity.service.IuserService接口代表了應用架構的業務層,而這對我們來說,是一個合適位置來添加方法級的安全。

添加@PreAuthorize方法註解

         我們第一個的設計決策就是要在業務層上添加方法安全,以保證用戶在修改密碼前已經作爲系統的合法用戶進行了登錄。這通過爲業務接口方法定義添加一個簡單的註解來實現,如下:

 

Java代碼  收藏代碼
  1. public interface IUserService {  
  2.   @PreAuthorize("hasRole('ROLE_USER')")  
  3.   public void changePassword(String username, String password);  
  4. }  

 這就是保證合法、已認證的用戶才能訪問修改密碼功能所要做的所有事情。Spring Security將會使用運行時的面向方面編程的切點(aspect oriented programming (AOP) pointcut)來對方法執行before advice,並在安全要求未滿足的情況下拋出AccessDeniedException異常。

 

讓Spring Security能夠使用方法註解

         我們還需要在dogstore-security.xml中做一個一次性的修改,通過這個文件我們已經進行了Spring Security其他的配置。只需要在<http>聲明之前,添加下面的元素即可:

 

Xml代碼  收藏代碼
  1. <global-method-security pre-post-annotations="enabled"/>  
 

 

校驗方法安全

         不相信如此簡單?那我們將ROLE_USER聲明修改爲ROLE_ADMIN。現在用用戶guest(密碼guest)登錄並嘗試修改密碼。你會在嘗試修改密碼時,看到如下的出錯界面:



 如果查看Tomcat的控制檯,你可以看到很長的堆棧信息,開始是這樣的:

 

Java代碼  收藏代碼
  1. DEBUG - Could not complete request  
  2. o.s.s.access.AccessDeniedException: Access is denied  
  3. at o.s.s.access.vote.AffirmativeBased.decide  
  4. at o.s.s.access.intercept.AbstractSecurityInterceptor.beforeInvocation  
  5. ...  
  6. at $Proxy12.changePassword(Unknown Source)  
  7. at com.packtpub.springsecurity.web.controller.AccountController.  
  8. submitChangePasswordPage  

          基於訪問拒絕的頁面以及指向changePassword方法的堆棧信息,我們可以看到用戶被合理的拒絕對業務方法的訪問,因爲缺少ROLE_ADMIN的GrantedAuthority。你可以測試修改密碼功能對管理員用戶依舊是可以訪問的。

 

         我們只是在接口上添加了簡單的聲明就能夠保證方法的安全,這是不是太令人興奮了?當然,我們不會願意Tomcat 原生的403錯誤頁面在我們的產品應用中出現——我們將會在第六章:高級配置與擴展講述訪問拒絕處理時,對其進行更新。

         讓我們介紹一下實現方法安全的其它方式,然後進入功能的背後以瞭解其怎樣以及爲什麼能夠生效。

 

幾種實現方法安全的方式

         除了@PreAuthorize註解以外,還有幾種其它的方式來聲明在方法調用前進行授權檢查的需求。我們會講解這些實現方法安全的不同方式,並比較它們在不同環境下的優勢與不足。

遵守JSR-250標準規則

         JSR-250, Common Annotations for the Java Platform定義了一系列的註解,其中的一些是安全相關的,它們意圖在兼容JSR-250的環境中很方便地使用。Spring框架從Spring 2.x釋放版本開始就兼容JSR-250,包括Spring Security框架。

         儘管JSR-250註解不像Spring原生的註解富有表現力,但是它們提供的註解能夠兼容不同的Java EE應用服務器實現如Glassfish,或面向服務的運行框架如Apache Tuscany。取決於你應用對輕便性的需求,你可能會覺得犧牲代碼的輕便性但減少對特定環境的要求是值得的。

         要實現我們在第一個例子中的規則,我們需要作兩個修改,首先在dogstore-security.xml文件中:

 

Xml代碼  收藏代碼
  1. <global-method-security jsr250-annotations="enabled"/>  

 其次,@PreAuthorize註解需要修改成@RolesAllowed註解。正如我們可能推斷出的那樣,@RolesAllowed註解並不支持SpEL表達式,所以它看起來很像我們在第二節中提到的URL授權。我們修改IuserService定義如下:

 

Java代碼  收藏代碼
  1. @RolesAllowed("ROLE_USER")  
  2. public void changePassword(String username, String password);  

 正如前面的練習那樣,如果不相信它能工作,嘗試修改ROLE_USER 爲ROLE_ADMIN並進行測試。

 

         要注意的是,也可以提供一系列允許的GrantedAuthority名字,使用Java 5標準的字符串數組註解語法:

 

Java代碼  收藏代碼
  1. @RolesAllowed({"ROLE_USER","ROLE_ADMIN"})  
  2. public void changePassword(String username, String password);  

          JSR-250還有兩個其它的註解:@PermitAll 和@DenyAll。它們的功能正如你所預想的,允許和禁止對方法的任何請求。

【類層次的註解。注意方法級別的安全註解也可以使用到類級別上!如果提供了方法級別的註解,將會覆蓋類級別的註解。如果業務需要在整個類上有安全策略的話,這會非常有用。要注意的是使用這個功能要有良好的註釋的編碼規範,這樣開發人員能夠很清楚的瞭解類和方法的安全特性。】

         我們將會在本章稍後的練習中介紹如何實現JSR-250風格的註解與Spring Security風格 的註解並存。

@Secured註解實現方法安全

         Spring本身也提供一個簡單的註解,類似於JSR-250 的@RolesAllowed註解。@Secured註解在功能和語法上都與@RolesAllowed一致。唯一需要注意的不同點是要使用這些註解的話,要在<global-method-security>元素中明確使用另外一個屬性:

 

Xml代碼  收藏代碼
  1. <global-method-security secured-annotations="enabled"/>  

 因爲@Secured與JSR標準的@RolesAllowed註解在功能上一致,所以並沒有充分的理由在新代碼中使用它,但是它能夠在Spring的遺留代碼中運行。

使用Aspect Oriented Programming (AOP)實現方法安全

         實現方法安全的最後一項技術也可能是最強大的方法,它還有一個好處是不需要修改源代碼。作爲替代,它使用面向方面的編程方式爲一個方法或方法集合聲明切點(pointcut),而增強(advice)會在切點匹配的情況下進行基於角色的安全檢查。AOP的聲明只在Spring Security的XML配置文件中並不涉及任何的註解。

         以下就是聲明保護所有的service接口只有管理權限才能訪問的例子:

 

Xml代碼  收藏代碼
  1. <global-method-security>  
  2.   <protect-pointcut access="ROLE_ADMIN"   
  3. expression="execution(* com.packtpub.springsecurity.service.I*Service.*(..))"/>  
  4. </global-method-security>  

 切點表達式基於Spring AOP對AspectJ的支持。但是,Spring AspectJ AOP僅支持AspectJ切點表達式語言的一個很小子集——可以參考Spring AOP的文檔以瞭解其支持的表達式和其它關於Spring AOP編程的重要元素。

         注意的是,可以指明一系列的切點聲明,以指向不同的角色和切點目標。以下的就是添加切點到DAO中一個方法的例子:

 

Xml代碼  收藏代碼
  1. <global-method-security>  
  2.   <protect-pointcut access="ROLE_USER"   
  3. expression="execution(* com.packtpub.springsecurity.dao.IProductDao.getCategories(..)) &amp;&amp;  
  4. args()"/>  
  5.   <protect-pointcut access="ROLE_ADMIN" expression="execution(* com.  
  6. packtpub.springsecurity.service.I*Service.*(..))"/>  
  7. </global-method-security>  

 注意在新增的切點中,我們添加了一些AspectJ的高級語法,來聲明Boolean邏輯以及其它支持的切點,而參數可以用來確定參數的類型聲明。

         同Spring Security其它允許一系列安全聲明的地方一樣,AOP風格的方法安全是按照從頂到底的順序進行的,所以需要按照最特殊到最不特殊的順序來寫切點。

         使用AOP來進行編程即便是經驗豐富的開發人員可能也會感到迷惑。如果你確定要使用AOP來進行安全聲明,除了Spring AOP的參考手冊外,強烈建議你參考一些這個專題相關的書籍。AOP實現起來比較複雜,尤其是在解決不按照你預期運行的配置錯誤時更是如此。

比較方法授權的類型

         以下的快速參考表可能在你選擇授權方法檢查時派上用場:

        

方法授權類型

聲明方式

JSR標準

允許SpEL表達式

@PreAuthorize

@PostAuthorize

註解

No

Yes

@RolesAllowed

@PermitAll

@DenyAll

註解

Yes

NO

@Secure

註解

No

No

protect-pointcut

XML

No

No

         大多數使用Java 5的Spring Security用戶傾向於使用JSR-250註解,以達到在IT組織間最大的兼容性和對業務類(以及相關約束)的重用。在需要的地方,這些基本的聲明能夠被Spring Security本身實現的註解所代替。

         如果你在不支持註解的環境中(Java 1.4或更早版本)中使用Spring Security,很不幸的是,關於方法安全的執行你的選擇可能會很有限。即使在這樣的情況下,對AOP的使用也提供了相當豐富的環境來開發基本的安全聲明。

方法的安全保護是怎樣運行的?

         方法安全的訪問決定機制——一個給定的請求是否被允許——在概念上與web請求的訪問決定邏輯是相同的。AccessDecisionManager使用一個AccessDecisionVoters集合,其中每一個都要對能否進行訪問做出允許、拒絕或者棄權的的投票。AccessDecisionManager彙集這些投票器的結果並形成一個最終能否允許處罰方法的決定。

         Web請求的訪問決策沒有這麼複雜,這是因爲通過ServletFilters對安全請求做攔截(以及請求拒絕)都相對很直接。因爲方法的觸發可能發生在任何的地方,包括沒有通過Spring Security直接配置的代碼,Spring Security的設計者於是選擇Spring管理的AOP方式來識別、評估以及保護方法的觸發。

         下圖在總體上展現了方法觸發授權決策的主要參與者:



          我們能夠看到Spring Security的o.s.s.access.intercept.aopalliance.MethodSecurityInterceptor被標準的Spring AOP運行時觸發以攔截感興趣的方法調用。通過上面的流程圖,是否允許方法調用的邏輯就相對很清晰了。

         此時,我們可能會比較關心方法安全功能的性能。顯然,MethodSecurityInterceptor不能在應用中每個方法調用的時候觸發——那方法或類上的註解是如何做到AOP攔截的呢?

         首先,AOP織入默認不會對所有Spring管理的bean觸發。相反,如果<global-method-security>在Spring Security配置中定義,一個標準的Spring AOP  o.s.beans.factory.config.BeanPostProcessor將會被註冊,它將會探查AOP配置是否有AOP增強器(advisors)需要織入(以及攔截)。這個工作流是Spring標準的AOP處理(名爲AOP自動織入),並不是Spring Security所特有的。所有的BeanPostProcessors在spring ApplicationContext初始化時執行,在所有的Spring Bean配置生效後。

         Spring的AOP自動織入功能查詢所有註冊的PointcutAdvisors,查看是否有AOP切點匹配方法的調用並使用AOP增強(advice)。Spring Security實現了o.s.s.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor類,它會檢查所有配置的方法安全病建立適當的AOP攔截。注意的是,只有聲明瞭方法安全的接口和類纔會被AOP代理。

【強烈建議在接口上聲明AOP規則(以及其它的安全註解),而不是在實現類上。使用類(通過Spring的CGLIB代理)進行聲明可能會導致應用出現不可預知的行爲改變,通常在正確性方面比不上在接口定義安全聲明(通過AOP)。】

MethodSecurityMetadataSourceAdvisor將AOP影響方法行爲的決定委託給o.s.s.access.method.MethodSecurityMetadataSource的實例。不同的方法安全註解都擁有自己的MethodSecurityMetadataSource,它將用來檢查每個方法和類並添加在運行時執行的增強(advice)。

以下的圖展現了這個過程是如何發生的:



          取決於你的應用中配置的Sprin Bean的數量,以及擁有的安全方法註解的數量,添加方法安全代理將會增加初始化ApplicationContext的時間。但是,一旦上下文初始化完成,對單個的代理bean來說性能的影響可以忽略不計了。

 

 

 

發佈了25 篇原創文章 · 獲贊 2 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章