天天看點

Spring Security(17)——基于方法的權限控制

目錄

<a href="#_Toc437270426">1.1     intercept-methods定義方法權限控制</a>

<a href="#_Toc437270427">1.2     使用pointcut定義方法權限控制</a>

<a href="#_Toc437270428">1.3     使用注解定義方法權限控制</a>

<a href="#_Toc437270429">1.3.1    JSR-250注解</a>

<a href="#_Toc437270430">1.3.2    @Secured注解</a>

<a href="#_Toc437270431">1.3.3    支援表達式的注解</a>

<a href="#_Toc437270432">1.4     方法權限控制的攔截器</a>

<a href="#_Toc437270433">1.4.1    MethodSecurityInterceptor</a>

<a href="#_Toc437270434">1.4.2    AspectJMethodSecurityInterceptor</a>

       之前介紹的都是基于URL的權限控制,Spring Security同樣支援對于方法的權限控制。可以通過intercept-methods對某個bean下面的方法進行權限控制,也可以通過pointcut對整個Service層的方法進行統一的權限控制,還可以通過注解定義對單獨的某一個方法進行權限控制。

       intercept-methods是需要定義在bean元素下的,通過它可以定義對目前的bean的某些方法進行權限控制,具體方法是使用其下的子元素protect進行定義的。protect元素需要指定兩個屬性,access和method,method表示需要攔截的方法名稱,可以使用通配符,access表示執行對應的方法需要擁有的權限,多個權限之間可以使用逗号分隔。

   &lt;bean id="userService" class="com.xxx.service.impl.UserServiceImpl"&gt;

      &lt;security:intercept-methods&gt;

         &lt;security:protect access="ROLE_USER" method="find*"/&gt;

         &lt;security:protect access="ROLE_ADMIN" method="add*"/&gt;

         &lt;security:protect access="ROLE_ADMIN" method="update*"/&gt;

         &lt;security:protect access="ROLE_ADMIN" method="delete*"/&gt;

      &lt;/security:intercept-methods&gt;

   &lt;/bean&gt;

       在上面的配置中表示在執行UserServiceImpl的方法名以find開始的方法時需要目前使用者擁有ROLE_USER的權限,在執行方法名以add、update或delete開始的方法時需要擁有ROLE_ADMIN的權限。當通路被拒絕時還是交由ExceptionTranslationFilter處理,這也就意味着如果使用者未登入則會引導使用者進行登入,否則預設将傳回403錯誤碼到用戶端。

       基于pointcut的方法權限控制是通過global-method-security下的protect-pointcut來定義的。可以在global-method-security元素下定義多個protect-pointcut以對不同的pointcut使用不同的權限控制。

   &lt;security:global-method-security&gt;

      &lt;security:protect-pointcut access="ROLE_READ" expression="execution(* com.elim.*..*Service.find*(..))"/&gt;

      &lt;security:protect-pointcut access="ROLE_WRITE" expression="execution(* com.elim.*..*Service.*(..))"/&gt;

   &lt;/security:global-method-security&gt;

       上面的定義表示我們在執行com.elim包或其子包下任意以Service結尾的類,其方法名以find開始的所有方法時都需要使用者擁有ROLE_READ的權限,對于com.elim包或其子包下任意以Service結尾的類的其它方法在執行時都需要ROLE_WRITE的權限。需要注意的是對應的類需要是定義在ApplicationContext中的bean才行。此外同對于URL的權限控制一樣,當定義多個protect-pointcut時更具有特性的應當先定義,因為在pointcut比對的時候是按照聲明順序進行比對的,一旦比對上了後續的将不再進行比對了。

       基于注解的方法權限控制也是需要通過global-method-security元素定義來進行啟用的。Spring Security在方法的權限控制上支援三種類型的注解,JSR-250注解、@Secured注解和支援表達式的注解。這三種注解預設都是沒有啟用的,需要單獨通過global-method-security元素的對應屬性進行啟用。

       要使用JSR-250注解,首先我們需要通過設定global-method-security元素的jsr250-annotation=”enabled”來啟用基于JSR-250注解的支援,預設為disabled。

       &lt;security:global-method-security jsr250-annotations="enabled"/&gt;

       此外,還需要確定添加了jsr250-api到我們的類路徑下。之後就可以在我們的Service方法上使用JSR-250注解進行權限控制了。

@Service

@RolesAllowed("ROLE_ADMIN")

public class UserServiceImpl implements UserService {

   public void addUser(User user) {

      System.out.println("addUser................" + user);

   }

   public void updateUser(User user) {

      System.out.println("updateUser.............." + user);

   @RolesAllowed({"ROLE_USER", "ROLE_ADMIN"})

   public User find(int id) {

      System.out.println("find user by id............." + id);

      return null;

   public void delete(int id) {

      System.out.println("delete user by id................");

   @RolesAllowed("ROLE_USER")

   public List&lt;User&gt; findAll() {

      System.out.println("find all user...............");

}

       上面的代碼表示執行UserServiceImpl裡面所有的方法都需要角色ROLE_ADMIN,其中findAll()方法的執行需要ROLE_USER角色,而find()方法的執行對于ROLE_USER或者ROLE_ADMIN角色都可以。

       順便介紹一下JSR-250中對權限支援的注解。

       RolesAllowed表示通路對應方法時所應該具有的角色。其可以标注在類上,也可以标注在方法上,當标注在類上時表示其中所有方法的執行都需要對應的角色,當标注在方法上表示執行該方法時所需要的角色,當方法和類上都使用了@RolesAllowed進行标注,則方法上的@RolesAllowed将覆寫類上的@RolesAllowed,即方法上的@RolesAllowed将對目前方法起作用。@RolesAllowed的值是由角色名稱組成的數組。

       PermitAll表示允許所有的角色進行通路,也就是說不進行權限控制。@PermitAll可以标注在方法上也可以标注在類上,當标注在方法上時則隻對對應方法不進行權限控制,而标注在類上時表示對類裡面所有的方法都不進行權限控制。(1)當@PermitAll标注在類上,而@RolesAllowed标注在方法上時則按照@RolesAllowed将覆寫@PermitAll,即需要@RolesAllowed對應的角色才能通路。(2)當@RolesAllowed标注在類上,而@PermitAll标注在方法上時則對應的方法也是不進行權限控制的。(3)當在方法上同時使用了@PermitAll和@RolesAllowed時先定義的将發生作用,而都定義在類上時則是反過來的,即後定義的将發生作用(這個沒多大的實際意義,實際應用中不會有這樣的定義)。

       DenyAll是和PermitAll相反的,表示無論什麼角色都不能通路。@DenyAll隻能定義在方法上。你可能會有疑問使用@DenyAll标注的方法無論擁有什麼權限都不能通路,那還定義它幹啥呢?使用@DenyAll定義的方法隻是在我們的權限控制中不能通路,脫離了權限控制還是可以通路的。

       @Secured是由Spring Security定義的用來支援方法權限控制的注解。它的使用也是需要啟用對應的支援才會生效的。通過設定global-method-security元素的secured-annotations=”enabled”可以啟用Spring Security對使用@Secured注解标注的方法進行權限控制的支援,其值預設為disabled。

   &lt;security:global-method-security secured-annotations="enabled"/&gt;

   @Secured("ROLE_ADMIN")

   @Secured("ROLE_USER")

       在上面的代碼中我們使用@Secured定義了隻有擁有ROLE_ADMIN角色的使用者才能調用方法addUser(),隻有擁有ROLE_USER角色的使用者才能調用方法findAll()。

       Spring Security中定義了四個支援使用表達式的注解,分别是@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter。其中前兩者可以用來在方法調用前或者調用後進行權限檢查,後兩者可以用來對集合類型的參數或者傳回值進行過濾。要使它們的定義能夠對我們的方法的調用産生影響我們需要設定global-method-security元素的pre-post-annotations=”enabled”,預設為disabled。

   &lt;security:global-method-security pre-post-annotations="disabled"/&gt;

使用@PreAuthorize和@PostAuthorize進行通路控制

       @PreAuthorize可以用來控制一個方法是否能夠被調用。

   @PreAuthorize("hasRole('ROLE_ADMIN')")

   @PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")

       在上面的代碼中我們定義了隻有擁有角色ROLE_ADMIN的使用者才能通路adduser()方法,而通路find()方法需要有ROLE_USER角色或ROLE_ADMIN角色。使用表達式時我們還可以在表達式中使用方法參數。

   /**

    * 限制隻能查詢Id小于10的使用者

    */

   @PreAuthorize("#id&lt;10")

      System.out.println("find user by id........." + id);

    * 限制隻能查詢自己的資訊

   @PreAuthorize("principal.username.equals(#username)")

   public User find(String username) {

      System.out.println("find user by username......" + username);

    * 限制隻能新增使用者名稱為abc的使用者

   @PreAuthorize("#user.name.equals('abc')")

   public void add(User user) {

      System.out.println("addUser............" + user);

       在上面代碼中我們定義了調用find(int id)方法時,隻允許參數id小于10的調用;調用find(String username)時隻允許username為目前使用者的使用者名;定義了調用add()方法時隻有當參數user的name為abc時才可以調用。

       有時候可能你會想在方法調用完之後進行權限檢查,這種情況比較少,但是如果你有的話,Spring Security也為我們提供了支援,通過@PostAuthorize可以達到這一效果。使用@PostAuthorize時我們可以使用内置的表達式returnObject表示方法的傳回值。我們來看下面這一段示例代碼。

   @PostAuthorize("returnObject.id%2==0")

      User user = new User();

      user.setId(id);

      return user;

       上面這一段代碼表示将在方法find()調用完成後進行權限檢查,如果傳回值的id是偶數則表示校驗通過,否則表示校驗失敗,将抛出AccessDeniedException。       需要注意的是@PostAuthorize是在方法調用完成後進行權限檢查,它不能控制方法是否能被調用,隻能在方法調用完成後檢查權限決定是否要抛出AccessDeniedException。

使用@PreFilter和@PostFilter進行過濾

       使用@PreFilter和@PostFilter可以對集合類型的參數或傳回值進行過濾。使用@PreFilter和@PostFilter時,Spring Security将移除使對應表達式的結果為false的元素。

   @PostFilter("filterObject.id%2==0")

      List&lt;User&gt; userList = new ArrayList&lt;User&gt;();

      User user;

      for (int i=0; i&lt;10; i++) {

         user = new User();

         user.setId(i);

         userList.add(user);

      }

      return userList;

       上述代碼表示将對傳回結果中id不為偶數的user進行移除。filterObject是使用@PreFilter和@PostFilter時的一個内置表達式,表示集合中的目前對象。當@PreFilter标注的方法擁有多個集合類型的參數時,需要通過@PreFilter的filterTarget屬性指定目前@PreFilter是針對哪個參數進行過濾的。如下面代碼就通過filterTarget指定了目前@PreFilter是用來過濾參數ids的。

   @PreFilter(filterTarget="ids", value="filterObject%2==0")

   public void delete(List&lt;Integer&gt; ids, List&lt;String&gt; usernames) {

      ...

       關于方法權限控制,Spring Security提供了兩類AbstractSecurityInterceptor,基于AOP Alliance的MethodSecurityInterceptor,和基于Aspectj繼承自MethodSecurityInterceptor的AspectJMethodSecurityInterceptor。

       當我們在使用基于NameSpace進行方法保護的配置時,Spring Security預設配置的就是MethodSecurityInterceptor。根據配置的不同,一個攔截器可能隻是針對于一個bean,也可能是針對于多個bean的。MethodSecurityInterceptor使用一個MethodSecurityMetadataSource的執行個體來擷取特定方法調用配置的ConfigAttribute。當我們在ApplicationContext配置檔案中使用intercept-methods元素或protect-point元素定義需要保護的方法調用時,Spring Security内部預設會使用一個MapBasedMethodSecurityMetadataSource來儲存在這些元素上定義的配置資訊,儲存的key是對應的方法名(可以是含有通配符的)。類似的使用JSR-250注解時将使用Jsr250MethodSecurityMetadataSource解析配置屬性;使用@Secured注解時将使用SecuredAnnotationSecurityMetadataSource解析配置屬性;使用pre-post-annotations時将使用PrePostAnnotationSecurityMetadataSource解析配置屬性。

       MethodSecurityInterceptor是實作了MethodInterceptor接口的,是以我們在使用Spring Aop時,可以自己配置一個MethodSecurityInterceptor的bean。

   &lt;!-- 自定義MethodSecurityInterceptor --&gt;

   &lt;bean id="methodSecurityInterceptor"

   class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor"&gt;

      &lt;property name="authenticationManager" ref="authenticationManager" /&gt;

      &lt;property name="accessDecisionManager" ref="accessDecisionManager" /&gt;

      &lt;property name="afterInvocationManager" ref="afterInvocationManager" /&gt;

      &lt;property name="securityMetadataSource"&gt;

         &lt;security:method-security-metadata-source&gt;

            &lt;!-- 指定需要受保護的方法和需要的權限 --&gt;

            &lt;security:protect method="com.xxx.service.UserService.find*"

                access="ROLE_USER" /&gt;

            &lt;security:protect method="com.xxx.service.UserService.delete*"

                access="ROLE_ADMIN" /&gt;

         &lt;/security:method-security-metadata-source&gt;

      &lt;/property&gt;

       定義了MethodSecurityInterceptor以後,我們需要類似AOP配置那樣,配置哪些該MethodInterceptor需要攔截哪些方法的執行。這種可選配置是很多種的,因為我們這裡隻是攔截UserService中的具體方法,是以就采用基于bean name的自動代理。

   &lt;!-- 基于bean的攔截 --&gt;

   &lt;bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"&gt;

      &lt;property name="interceptorNames"&gt;

         &lt;list&gt;

            &lt;value&gt;methodSecurityInterceptor&lt;/value&gt;

         &lt;/list&gt;

      &lt;property name="beanNames"&gt;

            &lt;value&gt;userService&lt;/value&gt;

       按照上面的配置,我們在通路UserService的find方法時就需要ROLE_USER的權限,而通路delete方法時則需要ROLE_ADMIN權限。

       AspectJMethodSecurityInterceptor是繼承自MethodSecurityInterceptor的,不同的是AspectJMethodSecurityInterceptor是用來支援AspectJ的JointPoint的,但在底層還是會把它封裝成一個MethodInvocation進行調用。

(注:本文是基于Spring Security3.1.6所寫)