目錄
<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表示執行對應的方法需要擁有的權限,多個權限之間可以使用逗号分隔。
<bean id="userService" class="com.xxx.service.impl.UserServiceImpl">
<security:intercept-methods>
<security:protect access="ROLE_USER" method="find*"/>
<security:protect access="ROLE_ADMIN" method="add*"/>
<security:protect access="ROLE_ADMIN" method="update*"/>
<security:protect access="ROLE_ADMIN" method="delete*"/>
</security:intercept-methods>
</bean>
在上面的配置中表示在執行UserServiceImpl的方法名以find開始的方法時需要目前使用者擁有ROLE_USER的權限,在執行方法名以add、update或delete開始的方法時需要擁有ROLE_ADMIN的權限。當通路被拒絕時還是交由ExceptionTranslationFilter處理,這也就意味着如果使用者未登入則會引導使用者進行登入,否則預設将傳回403錯誤碼到用戶端。
基于pointcut的方法權限控制是通過global-method-security下的protect-pointcut來定義的。可以在global-method-security元素下定義多個protect-pointcut以對不同的pointcut使用不同的權限控制。
<security:global-method-security>
<security:protect-pointcut access="ROLE_READ" expression="execution(* com.elim.*..*Service.find*(..))"/>
<security:protect-pointcut access="ROLE_WRITE" expression="execution(* com.elim.*..*Service.*(..))"/>
</security:global-method-security>
上面的定義表示我們在執行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。
<security:global-method-security jsr250-annotations="enabled"/>
此外,還需要確定添加了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<User> 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。
<security:global-method-security secured-annotations="enabled"/>
@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。
<security:global-method-security pre-post-annotations="disabled"/>
使用@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<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<User> userList = new ArrayList<User>();
User user;
for (int i=0; i<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<Integer> ids, List<String> 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。
<!-- 自定義MethodSecurityInterceptor -->
<bean id="methodSecurityInterceptor"
class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="afterInvocationManager" ref="afterInvocationManager" />
<property name="securityMetadataSource">
<security:method-security-metadata-source>
<!-- 指定需要受保護的方法和需要的權限 -->
<security:protect method="com.xxx.service.UserService.find*"
access="ROLE_USER" />
<security:protect method="com.xxx.service.UserService.delete*"
access="ROLE_ADMIN" />
</security:method-security-metadata-source>
</property>
定義了MethodSecurityInterceptor以後,我們需要類似AOP配置那樣,配置哪些該MethodInterceptor需要攔截哪些方法的執行。這種可選配置是很多種的,因為我們這裡隻是攔截UserService中的具體方法,是以就采用基于bean name的自動代理。
<!-- 基于bean的攔截 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>methodSecurityInterceptor</value>
</list>
<property name="beanNames">
<value>userService</value>
按照上面的配置,我們在通路UserService的find方法時就需要ROLE_USER的權限,而通路delete方法時則需要ROLE_ADMIN權限。
AspectJMethodSecurityInterceptor是繼承自MethodSecurityInterceptor的,不同的是AspectJMethodSecurityInterceptor是用來支援AspectJ的JointPoint的,但在底層還是會把它封裝成一個MethodInvocation進行調用。
(注:本文是基于Spring Security3.1.6所寫)