Spring Security提供了三种不同的安全注解:
- Spring Security自带的@Secured
- JSR-250的@RolesAllowed注解
- 表达式驱动的注解,@PreAuthorize、@PostAuthorize、@PreFilter、@PostFilter
按权限保护方法调用
@Secured和@RolesAllowed使用方法相同,不同的地方在于@RolesAllowed是Java标准中的,@Secured是Spring框架内的。
首先启用基于注解的方法安全性,通过继承GlobalMethodSecurityConfig类:
@Configuration
@EnableGlobalMethodSecurity(securedEnabled=true)
//如果使用@RolesAllowed注解需要改成 (jsr250Enabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfig{
}
与之前Web安全认证类似,不同的是Web安全认证的权限判断是通过重写configure()方法对AuthenticationManagerBuilder参数来配置,这里是使用注解配置在目标方法上。
@Secured("ROLE_SPITTLE", "ROLE_ADMIN")
//或@RolesAllowed("ROLE_SPITTLE", "ROLE_ADMIN")
public void addSpitle(Spittle spittle);
@Secure和@RolesAllowed使用一个String数组作为参数。每个String为一个权限,调用这个方法的用户至少需要具备其中一种权限才能进行调用。
这两种注解的弊端在于,只能通过权限来判断能否调用方法。
使用表达式保护方法调用
Spring Security提供了 4 个注解,可以使用SpEL表达式来保护方法调用:
注解 | 描述 |
---|---|
@PreAuthorize | 在方法调用之前,基于表达式计算的结果来限制对方法的访问 |
@PostAuthorize | 允许方法调用,但是如果表达式结果为false,将抛出一个安全性异常 |
@PostFilter | 允许方法调用,但必须按照表达式来过滤返回的结果 |
@PreFilter | 允许方法调用,但必须在进入方法前过滤输入值 |
首先需要将@EnableGlobalMethodSecurity注解的prePostEnabled属性设置为true,来启用它们:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{}
设定方法访问规则
@PreAuthorize和@PostAuthorize注解的区别在于表达式执行的时机,Pre在方法调用前,Post在方法调用后。
@PreAuthorize:
@PreAuthorize("hasRole('ROLE_SPITTLE')") //判断调用方法的用户是否有权限
@PreAuthorize("(hasRole('ROLE_SPITTLE') and #spittle.text.length() <= 140)"
+ "or hasRole('ROLE_VIP')" )//普通用户的文字最多140字。VIP用户无限制。spittle能直接引用传入的同名参数
public void addSpittle(Spittle spittle);
@PostAuthorize:
@PostAuthorize("returnObject.spittle.username == principal.username")
//判断通过查询id返回的用户是不是当前认证的用户
public Spittle getSpittleById(Long id);
returnObject变量可以访问方法的返回对象,principal代表当前认证用户的主要信息,这两个由Spring Security提供。
如果SpEL表达式判定为false,将会抛出一个AccessDeniedException异常,而调用者也不会获得结果。
过滤方法的输入和输出
有时需要保护的并不是方法的调用,而是方法传入和输出的数据。例如,getOffensiveSpittles()方法会返回标记为具有攻击性的Spittle列表,这个方法主要提供给管理员使用。但是,普通用户也可以调用这个方法来查看自己的Spittle是不是具有攻击性。
@PostFilter:
@PreAuthorize("hasAnyRole({'ROLE_SPITTLE', 'ROLE_ADMIN'})")
@PostFilter("hasRole('ROLE_ADMIN') || "
+ "filterObject.spittle.username == principal.username")//确保普通用户只能看到自己的spittle,如果返回的spittle的用户名和当前认证用户不同,就过滤掉它
public List<Spittle> getOffensiveSpittles();
除了事后过滤方法的返回值,还可以预先过滤传入到方法中的值,例如,批量删除Spittle,管理员能删除所有的,普通用户只能删除自己的Spittle
@PreAuthorize("hasAnyRole({'ROLE_SPITTLE', 'ROLE_ADMIN'})")
@ProFilter("hasRole('ROLE_ADMIN') || "
+ "targetObject.spittle.username == principal.name")//遍历数组中的spittle,过滤掉其它用户的spittle
public void deleteSpittles(List<Spittle> spittles);
targetObject代表当前的传入参数。
定义许可证计算器
如果安全规则变得复杂,定义在注解中的SpEL表达式会变得笨重而且难以测试。可以将它改为:
@ProFilter("hasPermission(targetObject, 'delete')")
hasPermission()是扩展的一个属性,使用它需要配置一个实现PermissionEvaluator接口的类:
//这段代码抄自https://www.cnblogs.com/fenglan/p/5913463.html
public class MyPermissionEvaluator implements PermissionEvaluator {
public boolean hasPermission(Authentication authentication,Object targetDomainObject, Object permission) {
if ("user".equals(targetDomainObject)) {
return this.hasPermission(authentication, permission);
}
return false;
}
//总是认为有权限
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return true;
}
//简单的字符串比较,相同则认为有权限
private boolean hasPermission(Authentication authentication, Object permission) {
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(permission)) {
returntrue;
}
}
return false;
}
}
这个接口实现了两个hasPermission,第一个把要评估的对象作为第二个参数。第二个hasPermission只有在目标对象的ID可以得到时才有用,并将id作为Serializable传入第二个参数。
许可计算器已经准备好了,还需要将它注册到Spring Security中。我们要替换原有的表达式计算器,默认情况下,它使用DefaultMethodSecurityExpressionHandler。我们要提供另一个DefaultMethodSecurityExpressionHandler,让它使用自定义的MyPermissionEvaluator:
这需要重载GlobalMethodSecurityConfiguration的createExpressionHandler方法:
@Override
protected MethodSecurityExpresionHandler createExpressionHandler(){
DefaultMethodSecurityExpressionHandler expresionHandler = new MyDefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new MyPermissionEvaluator);
return expressionHandler;
}
这样,不管在任何地方的表达式使用hasPermission来保护方法,都会调用MyPermissionEvaluator来决定用户是否由权限调用方法。