在本章中我们将会看到如何使用Spring Security保护bean方法。
在Spring Security中实现方法级安全性的最常见办法是使用特定的注解,将这些注解应用到需要保护的方法上。这样有几个好处,最重要的是当我们在编辑器中查看给定的方法时,能够很清楚地看到它的安全规则。
Spring Security提供了三种不同的安全注解:
在Spring中,如果要启用基于注解的方法安全性,关键之处在于要在配置类上使用@EnableGlobalMethodSecurity:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
}
GlobalMethodSecurityConfiguration类能够为方法级别的安全性提供更精细的配置。
如果securedEnabled属性的值为true的话,将会创建一个切点,这样的话Spring Security切面就会包装带有@Security注解的方法。(所以最好把要控制权限的方法放在service层)。
示例:
@Service
public class SecurityService {
@Secured("ROLE_ADMIN")
public void test1(){
System.out.println("拥有ADMIN权限");
}
@Secured({"ROLE_ADMIN","ROLE_USER"})
public void test2(){
System.out.println("拥有ADMIN或USER权限");
}
}
@Secured注解的不足之处在于它是spring特定的注解。如果更倾向于使用Java标准定义的注解,那么你应该考虑使用@RolesAllowed注解
@RolesAllowed是JSR-250定义的Java标准注解。如果选择@RolesAllowed的话,需要将@EnableGlobalMethodSecurity的jsr250Enabled属性设置为true,以开启此功能:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true,jsr250Enabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
}
使用示例:
@Service
public class SecurityService {
@RolesAllowed("ROLE_ADMIN")
public void test1(){
System.out.println("拥有ADMIN权限");
}
@RolesAllowed({"ROLE_ADMIN","ROLE_USER"})
public void test2(){
System.out.println("拥有ADMIN或USER权限");
}
}
Spring Security 3.0提供了4个新的注解,可以使用SpEL表达式来保护方法调用
注解 | 描述 |
---|---|
@PreAuthorize | 在方法调用之前,基于表达式的计算结果来限制对方法的访问 |
@PostAuthorize | 允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常 |
@PostFilter | 允许方法调用,但必须按照表达式来过滤方法的结果 |
@PreFilter | 允许方法调用,但必须在进入方法之前过滤输入值 |
首先,我们需要将@EnableGlobalMethodSecurity的prePostEnabled属性值设为true,从而开启它们:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true,jsr250Enabled=true,prePostEnabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
}
@PreAuthorize和@PostAuthorize能够基于表达式的计算结果来限制方法的访问。区别在于表达式执行的时机。@PreAuthorize的表达式会在方法调用之前执行,如果表达式的计算结果不为true的话,将会阻止方法执行。与之相反,@PostAuthorize的表达式知道方法返回才会执行,然后根据结果决定是否抛出安全性异常,如果为false,会抛出一个AccessDeniedException异常,而调用者也得不到返回的值。
@Service
public class SecurityService {
@PreAuthorize("hasRole('ROLE_ADMIN') and #num > 5")
public void test1(int num){
System.out.println("拥有ADMIN权限并且输入的数字大于5");
}
@PostAuthorize("returnObject == 'abc'")
public String test2(){
System.out.println("执行方法并返回一个字符串对象");
return "abcd";
}
}
为了便利地访问受保护方法的返回对象,Spring Security在SpEL中提供了名为returnObject的变量。
@PostFilter和@PreFilter都使用一个SpEL作为值参数,但是这个表达式不是用来限制方法访问的,@PostFilter会使用这个表达式计算该方法所返回集合的每个成员,将计算结果为false的成员从结果集中除掉,返回结果集。而@PreFilter过滤的是要进入方法中的集合,同样会对每一条数据进行检查,只有满足表达式(即表达式返回true)的结果才能保留在集中,最终传到方法里面。
@Service
public class SecurityService {
/**
* 需要拥有ADMIN或USER权限才能访问,
* ADMIN缺陷的话返回所有数据,
* USER权限的话只返回和登录用户相同用户名的数据
* @return
*/
@PreAuthorize("hasAnyRole({'ROLE_ADMIN','ROLE_USER'})")
@PostFilter("hasRole('ROLE_ADMIN') || "
+ "filterObject.username == principal.username")
public List test1(){
User u1 = new User("xuexiaoqiang");
User u2 = new User("admin");
User u3 = new User("xueqiang");
List list = new ArrayList();
list.add(u1);
list.add(u2);
list.add(u3);
return list;
}
/**
* 需要拥有ADMIN或USER权限才能访问,
* 拥有ADMIN权限的用户会将数据全部传入方法中
* 拥有USER权限的用户只会将相同用户名的数据传进去
* @param users
*/
@PreAuthorize("hasAnyRole({'ROLE_ADMIN','ROLE_USER'})")
@PreFilter("hasRole('ROLE_ADMIN') || "
+ "filterObject.username == principal.username")
public void test2(List users){
for(User u : users){
System.out.println(u.getUsername());
}
}
}
filterObject对象引用的是传入或传出的List中的某一个元素。