目錄spring
1.1 intercept-methods定義方法權限控制express
1.2 使用pointcut定義方法權限控制api
1.3 使用註解定義方法權限控制數組
1.3.1 JSR-250註解post
1.3.2 @Secured註解代理
1.3.3 支持表達式的註解對象
1.4 方法權限控制的攔截器繼承
1.4.1 MethodSecurityInterceptor接口
1.4.2 AspectJMethodSecurityInterceptorip
以前介紹的都是基於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...............");
return null;
}
}
上面的代碼表示執行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"/>
@Service
public class UserServiceImpl implements UserService {
@Secured("ROLE_ADMIN")
public void addUser(User user) {
System.out.println("addUser................" + user);
}
@Secured("ROLE_USER")
public List<User> findAll() {
System.out.println("find all user...............");
return null;
}
}
在上面的代碼中咱們使用@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能夠用來控制一個方法是否可以被調用。
@Service
public class UserServiceImpl implements UserService {
@PreAuthorize("hasRole('ROLE_ADMIN')")
public void addUser(User user) {
System.out.println("addUser................" + user);
}
@PreAuthorize("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')")
public User find(int id) {
System.out.println("find user by id............." + id);
return null;
}
}
在上面的代碼中咱們定義了只有擁有角色ROLE_ADMIN的用戶才能訪問adduser()方法,而訪問find()方法須要有ROLE_USER角色或ROLE_ADMIN角色。使用表達式時咱們還能夠在表達式中使用方法參數。
public class UserServiceImpl implements UserService {
/**
* 限制只能查詢Id小於10的用戶
*/
@PreAuthorize("#id<10")
public User find(int id) {
System.out.println("find user by id........." + id);
return null;
}
/**
* 限制只能查詢本身的信息
*/
@PreAuthorize("principal.username.equals(#username)")
public User find(String username) {
System.out.println("find user by username......" + username);
return null;
}
/**
* 限制只能新增用戶名稱爲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")
public User find(int id) {
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")
public List<User> findAll() {
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>
</bean>
定義了MethodSecurityInterceptor之後,咱們須要相似AOP配置那樣,配置哪些該MethodInterceptor須要攔截哪些方法的執行。這種可選配置是不少種的,由於咱們這裏只是攔截UserService中的具體方法,因此就採用基於bean name的自動代理。
<!-- 基於bean的攔截 -->
<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>methodSecurityInterceptor</value>
</list>
</property>
<property name="beanNames">
<list>
<value>userService</value>
</list>
</property>
</bean>
按照上面的配置,咱們在訪問UserService的find方法時就須要ROLE_USER的權限,而訪問delete方法時則須要ROLE_ADMIN權限。
AspectJMethodSecurityInterceptor是繼承自MethodSecurityInterceptor的,不一樣的是AspectJMethodSecurityInterceptor是用來支持AspectJ的JointPoint的,但在底層仍是會把它封裝成一個MethodInvocation進行調用。
(注:本文是基於Spring Security3.1.6所寫)