【Spring Security】 拦截器
【Spring Security】 拦截器
Metadata
title: 【Spring Security】 拦截器
date: 2023-02-03 10:50
tags:
- 行动阶段/完成
- 主题场景/组件
- 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
- 细化主题/Module/SpringSecurity
categories:
- SpringSecurity
keywords:
- SpringSecurity
description: 【Spring Security】 拦截器
【Spring Security】 拦截器
Security 中有各种各样的过滤器,也存在着拦截器,比如 MethodSecurityInterceptor。
**过滤器 (Filter)**:它依赖于 servlet 容器。在实现上,基于函数回调,它可以对几乎所有请求进行过滤。
拦截器(Interceptor):拦截器(Interceptor)是基于 Java 的[[../../../BasicsSpace/Language/Java/Java 基础/【Java 基础】 反射|反射机制]],比如 Spring 中的 AOP,可以对方法运行时进行拦截增强。
MethodInterceptor
MethodInterceptor 接口位于 spring [[../../../SpringSpace/SpringFramework/Spring 面向切面 AOP|aop]] 包下。顾名思义,是一个方法拦截器,在调用目标对象的方法时,就可以实现在调用方法之前、调用方法过程中、调用方法之后对其进行控制。
也可以使用 @Aspect 注解来实现。
入门示例
undefined
AbstractSecurityInterceptor
AbstractSecurityInterceptor 是一个抽象类,注释说这是受保护对象实现安全拦截的抽象类,顾名思义,是对未放行资源保护的拦截器。
成员属性
可以看出,AbstractSecurityInterceptor 维护了很多管理器,用于保护资源。
// Spring MessageSource访问
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 时间发布器
private ApplicationEventPublisher eventPublisher;
// 鉴权管理器
private AccessDecisionManager accessDecisionManager;
// 后置处理器
private AfterInvocationManager afterInvocationManager;
// 认证管理器
private AuthenticationManager authenticationManager = new NoOpAuthenticationManager();
// 当前访问对象管理器,用于创建临时的Authentication对象
private RunAsManager runAsManager = new NullRunAsManager();
// 是否重新认证
private boolean alwaysReauthenticate = false;
// 是否拒绝公共调用
private boolean rejectPublicInvocations = false;
// 是否验证配置属性
private boolean validateConfigAttributes = true;
// 是否发布发布授权成功
private boolean publishAuthorizationSuccess = false;
核心方法
既然是拦截器,那么肯定提供了前置和后置方法,前置校验当前请求是否合法,进行授权判断,拒绝后抛出异常,通过后,执行请求,并最终执行后置方法。
由于这个一个抽象类,肯定只定义的一些逻辑方法,具体执行都是子类去调用的。
可以看到,在前置方法中,会进行授权判断,未通过,抛出 AccessDeniedException,被 ExceptionTranslationFilter 捕获并处理。
/**
* 获取权限信息并进行授权判断,
*/
protected InterceptorStatusToken beforeInvocation(Object object) {
Assert.notNull(object, "Object was null");
// 1. 判断object是不是FilterInvocation
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());
}
// 2. 获取配置的访问控制规则 any request =》authenticated ,没有配置,return null
Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);
if (CollectionUtils.isEmpty(attributes)) {
Assert.isTrue(!this.rejectPublicInvocations,
() -> "Secure object invocation " + object
+ " was denied as public invocations are not allowed via this interceptor. "
+ "This indicates a configuration error because the "
+ "rejectPublicInvocations property is set to 'true'");
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized public object %s", object));
}
publishEvent(new PublicInvocationEvent(object));
return null; // no further work post-invocation
}
// 3. 判断认证对象Authentication是否为null
if (SecurityContextHolder.getContext().getAuthentication() == null) {
credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound",
"An Authentication object was not found in the SecurityContext"), object, attributes);
}
// 4. 获取Authentication对象
Authentication authenticated = authenticateIfRequired();
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));
}
// Attempt authorization
// 5. 进行授权判断
attemptAuthorization(object, attributes, authenticated);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));
}
// 6. 发布授权成功
if (this.publishAuthorizationSuccess) {
publishEvent(new AuthorizedEvent(object, attributes, authenticated));
}
// Attempt to run as a different user
// 7. 对Authentication进行再处理,这里没有处理,直接返回null
Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);
if (runAs != null) {
SecurityContext origCtx = SecurityContextHolder.getContext();
SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());
SecurityContextHolder.getContext().setAuthentication(runAs);
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));
}
// need to revert to token.Authenticated post-invocation
return new InterceptorStatusToken(origCtx, true, attributes, object);
}
this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");
// no further work post-invocation
// 8. 返回InterceptorStatusToken
return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);
}
后置处理器,主要用于对结果集进行处理。
/**
* 后置处理,可以对受保护对象返回的结果进行处理
* @param token beforeInvocation 返回拦截处理信息对象
* @param returnedObject 方法执行后 调用返回的任何对象
* @return 返回结果集
*/
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
// 1. token为null. 直接返回结果集
if (token == null) {
// public object
return returnedObject;
}
finallyInvocation(token); // continue to clean in this method for passivity
if (this.afterInvocationManager != null) {
// Attempt after invocation handling
try {
// 2. 调用后置处理器,处理结果集
returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
token.getSecureObject(), token.getAttributes(), returnedObject);
} catch (AccessDeniedException ex) {
// 3. 有异常直接抛出
publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(),
token.getSecurityContext().getAuthentication(), ex));
throw ex;
}
}
return returnedObject;
}
FilterSecurityInterceptor
FilterSecurityInterceptor 是 AbstractSecurityInterceptor 的其中一个子类。虽然继承了拦截器,又实现了 Filter 接口,实际还是一个过滤器,只是调用了拦截器中的相关方法。
源码注释中说这是一个,实现通过过滤器实现对 HTTP 资源进行安全处理的类。也就是说,这个拦截器拦截 HTTP 请求,进行拦截鉴权处理。
从拦截器链中看,它是最后一个过滤器。可以看做过滤器链的出口,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由 ExceptionTranslationFilter 过滤器进行捕获和处理。
可以从它的 invoke 方法中看出,主要是调用了父类的方法进行前置鉴权、后置结果集处理。
/**
* doFilter实际执行的方法
* @param filterInvocation 封装了request response 过滤器链的对象
*/
public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {
// 1. 如果已经执行过该过滤器,直接放行
if (isApplied(filterInvocation) && this.observeOncePerRequest) {
// filter already applied to this request and user wants us to observe
// once-per-request handling, so don't re-do security checking
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
return;
}
// first time this request being called, so perform security checking
// 2. 第一次调用这个请求,所以执行安全检查
if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {
// 3. 在request中添加__spring_security_filterSecurityInterceptor_filterApplied = true,表示执行了该过滤器
filterInvocation.getRequest().setAttribute(FILTER_APPLIED, Boolean.TRUE);
}
// 4. 前置访问控制处理
InterceptorStatusToken token = super.beforeInvocation(filterInvocation);
try {
// 5. 放行
filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());
} finally {
super.finallyInvocation(token);
}
// 6. 后置处理
super.afterInvocation(token, null);
}
MethodSecurityInterceptor
MethodSecurityInterceptor 也继承自 AbstractSecurityInterceptor,实现了 MethodInterceptor 接口。其功能也是对资源访问进行鉴权,那么和 FilterSecurityInterceptor 有什么区别呢?
一个是过滤器,一个是方法拦截器,所以他们的拦截对象是不同的,一个是拦截 Http 请求,一个是拦截方法执行。
在执行父类 beforeInvocation 方法时传入的参数是不一样的,FilterSecurityInterceptor 传入的是 FilterInvocation,MethodInterceptor 传入的是 MethodInvocation。
维护的 SecurityMetadataSource 不一样,SecurityMetadataSource 是存储和获取授权配置的接口,MethodSecurityInterceptor 中维护的是 MethodSecurityMetadataSource,FilterSecurityInterceptor 维护的是 FilterInvocationSecurityMetadataSource。
FilterInvocationSecurityMetadataSource 存放的是 HttpSecurity 配置类中配置的授权规则。FilterSecurityInterceptor 会从这里获取到这些规则,然后对其进行授权判断。
MethodSecurityMetadataSource 存放的是所以类和方法,会根据当前执行的类和方法,去内存中遍历,查询到当前执行方法配置的权限注解,然后对其进行授权判断。
总结
由上可知,FilterSecurityInterceptor 是拦截请求,然后从配置类中获取缓存的授权规则,而 MethodSecurityInterceptor 是拦截方法,从缓存中查询当前方法配置的权限注解规则,然后都调用父类的前置、后置方法,进行授权判断处理。所以一次请求,会先执行 FilterSecurityInterceptor,然后再执行 MethodSecurityInterceptor。