前言
Spring Security有两种配置方式,今天重点是绍基于方法配置的方式。
基于方法配置权限
这个主要是有一些注解提供给大家使用,今天来给大家一个demo(参考自官方sample)。
maven就不多累赘了。重点看看配置。
- 基于角色配置
/**
* 启用方法安全:@EnableMethodSecurity
*
* 启用@secured注解: **securedEnabled = true**
* <p>会导入配置:SecuredMethodSecurityConfiguration<p>
*
* 启用@PreAuthorize@PostAuthorize@PreFilter@PostFilter:**prePostEnabled = false**
* <p>会导入配置:PrePostMethodSecurityConfiguration</p>
*
* 启用jsr250相关的安全注解:**jsr250Enabled = true**
* <p>会导入配置:Jsr250MethodSecurityConfiguration</p>
* <p>jsr250包括@RolesAllowed@PermitAll@DenyAll</p>
*/
@Configuration
@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true, jsr250Enabled = true)
public class AspectjSecurityConfig {
}
/**
* 这个类的所有方法都需要有ROLE_USER角色才能执行
*/
@Service
@Secured("ROLE_USER")
public class SecuredService {
public void secureMethod() {
// nothing
}
}
@Service
public class SecuredService {
public void publicMethod() {
// nothing
}
/**
* 这个方法需要ROLE_USER才能执行
* 这三种配置方式都是等价的,只是提供支持的Advice不同。
*/
@Secured("ROLE_USER")
// @RolesAllowed("ROLE_USER")
// @PreAuthorize("hasRole('ROLE_USER')")
public void secureMethod() {
// nothing
}
@PreAuthorize("arguments[0] ne 'tony'")
// @PreAuthorize("filterObject ne 'tony'")
public void preAuthorize(@RequestParam("userCode") String userCode) {
// @preAuthorize不会赋值ROOT的filterObject和returnObject,因此无法使用入参。只能使用MethodSecurityExpressionRoot的其他方法
// 这个方法的实验现象为:不管传什么都能通过表达式
logger.info("preAuthorize:{}", userCode);
// nothing
}
/**
* 这个入参会被过滤地只剩下与authentication.name一样的
*/
@PreFilter("filterObject == authentication.name")
public void preFilter(@RequestParam("userCodeList") List<String> userCodeList) {
// http://localhost:8090/foo/preFilter?userCodeList=leo,tony
logger.info("preAuthorize:{}", userCode);
}
/**
* 这个方法需要ROLE_USER才能执行
*/
@PostAuthorize("returnObject ne 'tony'")
public String postAuthorize(@RequestParam("userCode") String userCode) {
// 传入的是不是tony,会抛出异常: 403
logger.info("postAuthorize:{}", userCode);
return userCode;
}
@GetMapping("/preAuthorizeSpel")
@PreAuthorize("hasAuthority('permission:read') || hasRole('ADMIN')")
public void preAuthorizeHasRole() {
logger.info("preAuthorizeSpel:{}", userCode);
// nothing
}
/**
* 这个方法需要ROLE_USER才能执行
*/
@PostFilter("returnObject == authentication.name")
public List<String> PostFilter(@RequestParam("userCodeList") List<String> userCodeList) {
// 实验结果就是,传入了的参数有值,但只有跟用户名一样的才会返回
logger.info("PostFilter:{}", userCodeList);
return userCodeList;
}
}
以上就是怎么使用,接着我们看下是如何实现的。
基于方法授权方式的实现原理
前面我们说过,是基于AOP实现的。那么,现在我们从源码层面来看看。
我们可以看到上面按照@EnableMethodSecurity的配置,分别对应地导入三个配置。
但是,我们可以先从AOP的角度设想一下,我们需要的是哪种类型的通知?不妨归类一下:
注解类型 | 通知类型 | 描述/备注 |
---|---|---|
@PreFilter@PreAuthorize@Secured以及JSR-250的相关注解 | 前置通知 | 这些很明显都是需要先校验/过滤参数再执行目标方法 |
@PostFilter@PostAuthorize | 后置通知 | 先执行目标方法,再校验/过滤结果集 |
接着我们来看看Spring Security的设计:
AuthorizationManagerBeforeMethodInterceptor
在执行方法之前进行鉴权,这也意味着,当权限不足时,他会抛出异常。
实际上,他是一个通用的增强,全取决于你怎么使用它。
在SpringSecurity的配置里,他可以负责@PreAuthor,也可以负责@Secured,甚至还能负责jsr250的相关注解。
需要提醒一点,一个实例对象只能一种注解哈。
但是,大家有没有想过一个问题:为什么@PreAuthor@Secured以及jsr250的相关注解,都能交给他来处理?
又或者说,他的设计是如何将这三者的处理抽象统一起来的?更具体一点,此三者有何共同之处,可以进行抽象和统一的??
要回答这个问题,我们需要先从这个三者的入手:
共同点:
都需要在执行方法前进行权限校验,校验不通过则都需要抛出异常,阻断方法调用。
异同点:
注解 | 配置描述 | 执行 |
---|---|---|
@PreAuthorize | 配置的是SPEL表达式 | 通过执行表达式来得出是否满足访问权限 |
@Secured | 指定角色 | 需要校验当前用户是否拥有指定角色 |
jsr250的@PermitAll | - | 任何人都可以访问 |
jsr250的@DenyAll | - | 任何人都不能访问 |
jsr250的@RolesAllowed | 指定角色 | 需要校验当前用户是否拥有指定角色 |
从共同点出发,本质上无非就是鉴权嘛,这不是很符合Spring Security的AuthorizationManager的职责吗?
然后就是不同点,我们发现无非就是权限的配置来源不同需要解析不同的注解咯。
有了这个思路,接下来我们翻找源码,理解起来就容易多了。
public final class AuthorizationManagerBeforeMethodInterceptor
implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean {
// 构造器,需要传入Pointcut,和AuthorizationManager<MethodInvocation>,以便校验权限。
public AuthorizationManagerBeforeMethodInterceptor(Pointcut pointcut,
AuthorizationManager<MethodInvocation> authorizationManager) {
Assert.notNull(pointcut, "pointcut cannot be null");
Assert.notNull(authorizationManager, "authorizationManager cannot be null");
this.pointcut = pointcut;
this.authorizationManager = authorizationManager;
}
/**
* 创建负责处理@PreAuthorize的增强
*/
public static AuthorizationManagerBeforeMethodInterceptor preAuthorize(
PreAuthorizeAuthorizationManager authorizationManager) {
AuthorizationManagerBeforeMethodInterceptor interceptor = new AuthorizationManagerBeforeMethodInterceptor(
AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager);
interceptor.setOrder(