0%

spring security 鉴权

FilterSecurityInterceptor

介绍

FilterSecurityInterceptor是过滤链的最后一环,一个请求完成了认证,且没有抛出异常之后就会到达FilterSecurityInterceptor所负责的鉴权部分,也就是说鉴权的入口就在FilterSecurityInterceptor

​ 它实现了Filter接口,我们一般直接继承这个过滤器或者继承他的父类,目的是为了注入自定义的授权管理器AccessDecisionManager、和权限元数据FilterInvocationSecurityMetadataSource

​ 它是在WebSecurityConfigurerAdapterinit()里配置的。

分析

进入FilterSecurityInterceptor的doFilter()、invoke()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter{
public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
//创建了一个FilterInvocation对象,这个FilterInvocation对象你可以当作它封装了request,它的主要工作就是拿请求里面的信息,比如请求的URI。
FilterInvocation fi = new FilterInvocation(request, response, chain);
//调用了自身的invoke方法,并将FilterInvocation对象传入。
invoke(fi);
}

public void invoke(FilterInvocation fi) throws IOException, ServletException {
if ((fi.getRequest() != null)
&& (fi.getRequest().getAttribute(FILTER_APPLIED) != null)
&& observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
else {
// first time this request being called, so perform security checking
if (fi.getRequest() != null && observeOncePerRequest) {
fi.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}

//根据资源权限配置来判断当前请求是否有权限访问对应的资源。
//如果不能访问,则抛出相应的异常
InterceptorStatusToken token = super.beforeInvocation(fi);

try {
//访问相关资源,通过SpringMvc的核心组件DispatcherServlet进行访问
fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
}
finally {
super.finallyInvocation(token);
}

super.afterInvocation(token, null);
}
}
}

调用父类AbstractSecurityInterceptor的beforeInvocation()方法

​ 1、获取当前资源权限(我们自己定义的规则权限)

​ 2、获取认证的authentication身份信息

​ 3、调用决策器尝试进行认证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public abstract class AbstractSecurityInterceptor implements InitializingBean,ApplicationEventPublisherAware, MessageSourceAware {
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
final boolean debug = logger.isDebugEnabled();

if (!getSecureObjectClass().isAssignableFrom(object.getClass())) {
throw new IllegalArgumentException(
"Security invocation attempted for object "
+ object.getClass().getName()
+ " but AbstractSecurityInterceptor only configured to support secure objects of type: "
+ getSecureObjectClass());
}

//调用子类的FilterInvocationSecurityMetadataSource.getAttributes(object)获取当前资源所需权限
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource()
.getAttributes(object);
//略

//如果Authentication.isAuthenticated()返回false或属性alwaysReauthenticate已设置为true,则检查当前身份验证令牌并将其传递给AuthenticationManager进行认证
//返回认证的authentication身份信息,并放入安全上下文
Authentication authenticated = authenticateIfRequired();

// Attempt authorization
try {
//调用访问决策器进行决策,尝试进行鉴权
this.accessDecisionManager.decide(authenticated, object, attributes);
}
catch (AccessDeniedException accessDeniedException) {
publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated,
accessDeniedException));

throw accessDeniedException;
}

//略
}
}

AccessDecisionManager

介绍

AccessDecisionManager访问决策管理器是一个接口,它声明了三个方法,除了第一个鉴权方法以外,还有两个是辅助性的方法,其作用都是甄别 decide方法中参数的有效性。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface AccessDecisionManager {

// 解决传递参数的访问控制决策
void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException,
InsufficientAuthenticationException;

//指示此AccessDecisionManager是否能够处理通过传递的ConfigAttribute提出的授权请求。
boolean supports(ConfigAttribute attribute);

//指示AccessDecisionManager实现是否能够为指示的安全对象类型提供访问控制决策
boolean supports(Class<?> clazz);
}

​ 从图中我们可以看到它主要有三个实现类,分别代表了三种不同的鉴权逻辑:

  • AffirmativeBased:一票通过,只要有一票通过就算通过,默认是它。
  • UnanimousBased:一票反对,只要有一票反对就不能通过。
  • ConsensusBased:少数票服从多数票。

这三个实现类,其实还不是真正判断请求能不能通过的类,真正判断请求是否通过的是投票器,然后实现类把投票器的结果综合起来来决定到底能不能通过。

分析

AffirmativeBased为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class  extends AbstractAccessDecisionManager {

public AffirmativeBased(List<AccessDecisionVoter<?>> decisionVoters) {
super(decisionVoters);
}

public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
//拒绝数量
int deny = 0;
//遍历从构造传入的决策投票器集合
for (AccessDecisionVoter voter : getDecisionVoters()) {
//调用该投票器的投票逻辑方法进行判断(AffirmativeBased默认传入WebExpressionVoter)
int result = voter.vote(authentication, object, configAttributes);

switch (result) {
//赞成
case AccessDecisionVoter.ACCESS_GRANTED:
return;
//拒绝
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
break;
default:
break;
}
}

//拒绝数量大于0,抛出AccessDeniedException异常
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}

//如果全部都弃权,进入此方法,判断此决策器是否允许全部弃权,不允许则抛出AccessDeniedException异常
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
}

当走完决策流程没有抛出AccessDeniedException异常,则权限校验通过

异常捕获

通过实现AccessDeniedHandler接口的handle()可以对AccessDeniedException异常抛出的权限被拒信息作出友好提示

1
2
3
//处理异常情况:认证失败和权限不足
//WebSecurityConfigurerAdapter配置
http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint).accessDeniedHandler(myAccessDeniedHandler);

流程图

自定义鉴权

方式一 访问决策管理器实现鉴权

1
2
3
1、自定义AccessDecisionManager访问决策管理器实现类,直接在决策器decide()方法实现鉴权逻辑,空参构造,不注入投票器
2、覆写FilterInvocationSecurityMetadataSource自定义权限规则逻辑,AbstractSecurityInterceptor.beforeInvocation()方法中需用到决策器的权限规则信息
3、覆写AbstractSecurityInterceptor实现类,使用第一步的自定义访问决策器、第二步的自定义安全元数据;将自定义的过滤器配置在FilterSecurityInterceptor之前 http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

​ 总结:请求经过自定义安全过滤器后,根据自定义安全元数据获取权限规则,来到自定义访问决策器走自定义的鉴权规则

方式二 投票器实现鉴权逻辑

1
2
3
4
1、自定义AccessDecisionVoter<FilterInvocation>投票器实现类,vote()方法中实现鉴权逻辑
2、自定义AccessDecisionManager访问决策管理器实现类,有参构造,注入自定义投票器
3、覆写FilterInvocationSecurityMetadataSource自定义权限规则逻辑,AbstractSecurityInterceptor.beforeInvocation()方法中需用到决策器的权限规则信息
4、覆写AbstractSecurityInterceptor实现类,使用第一步的自定义访问决策器、第二步的自定义安全元数据;将自定义的过滤器配置在FilterSecurityInterceptor之前 http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);

​ 总结:请求经过自定义安全过滤器后,根据自定义安全元数据获取权限规则,来到自定义访问决策器调用自定义投票器走自定义的投票鉴权逻辑;这种方式较完整的仿照security流程

以上自定义方式之前都需增加额外过滤器,用来拦截检测token状态

1
2
//在 UsernamePasswordAuthenticationFilter之前添加自定义OncePerRequestFilter过滤器实现类,目的是检查token状态是否正常(过期、系统已清除等)
http.addFilterBefore(myJwtAuthenticationTokenRequestFilter,UsernamePasswordAuthenticationFilter.class);