目录
1、过滤器的视角
2、DelegatingFilterProxy 委派过滤器代理(类)
2、FilterChainProxy 过滤器链代理(类)
4、SecurityFilterChain 安全过滤器链(接口)
5、Security Filters 安全过滤器实例
6、Spring Security 如何处理安全异常?
7、在认证的时候保存用户请求
// 释义、解读和思考
1、过滤器的视角
Spring Security 对 Servlet 支持基于 Servlet 过滤器。下图显示了单个HTTP请求的处理程序的典型分层。
客户端向应用程序发送请求,容器根据请求 URI 的路径创建一个 FilterChain,其中包含过滤器实例和处理 HttpServletRequest 的 Servlet。// 过滤器链式顺序执行的,具有有序性
因为 Filter 会影响下游的 Filter 实例和 Servlet,所以调用每个 Filter 的顺序非常重要。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 省略...
chain.doFilter(request, response);
// 省略...
}
// doFilter 方法中有一个 FilterChain 入参,FilterChain 也是一个接口,既然 FilterChain 是一个过滤器链,那么它的实现类中肯定包含有一个 Filter 列表(持有一个或多个Filter 对象)。看源码
// Spring Security 利用过滤器链来实现身份验证和授权
2、DelegatingFilterProxy 委派过滤器代理(类)
Spring 提供了一个名为 DelegatingFilterProxy 的过滤器代理类,它是 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间架桥。//这个类是一个通用的过滤器实例
// 在Spring 中,Spring 管理过滤器的生命周期,所以 Filter 实例也是 Spring 中的一个Bean
DelegatingFilterProxy 从 ApplicationContext 中查找 Bean Filter-0,然后调用 Bean Filter-0,下面的代码展示了这一过程:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
//获取Bean
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response);
}
// 总结:DelegatingFilterProxy 这个类就是一个过滤器实例,Spring 通过这个过滤器,去实现一系列的认证和授权操作。
// 深刻认识下,想想这种做法。
2、FilterChainProxy 过滤器链代理(类)
Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。
FilterChainProxy 是 Spring Security 提供的一个特殊过滤器,它允许通过 SecurityFilterChain 向多个过滤器实例委托。FilterChainProxy 是一个Bean,它通常被封装在 DelegatingFilterProxy中。
//见名知意,过滤器链的代理 FilterChainProxy 中会持有过滤器链的对象
public class FilterChainProxy extends GenericFilterBean {
private static final Log logger = LogFactory.getLog(FilterChainProxy.class);
private final static String FILTER_APPLIED = FilterChainProxy.class.getName().concat(
".APPLIED");
// SecurityFilterChain 对象列表
private List<SecurityFilterChain> filterChains;
//省略...
}
// FilterChainProxy -> DelegatingFilterProxy,可以猜想 DelegatingFilterProxy 中的这个 delegate 是不是就是 FilterChainProxy 的实例?源码见分晓
public class DelegatingFilterProxy extends GenericFilterBean {
@Nullable
private String contextAttribute;
@Nullable
private WebApplicationContext webApplicationContext;
@Nullable
private String targetBeanName;
private boolean targetFilterLifecycle;
@Nullable
private volatile Filter delegate; //这里有一个过滤器委托,proxy
private final Object delegateMonitor;
//省略...
}
4、SecurityFilterChain 安全过滤器链(接口)
FilterChainProxy 使用 SecurityFilterChain 来确定应该为当前请求调用哪一个 Spring SecurityFilter 实例。
//选择过滤器链,在FilterChainProxy 中是这样做的
// 返回与提供的URL匹配的第一个过滤器链
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
//一些源码:
//SecurityFilterChain(接口) -> DefaultSecurityFilterChain(实现类) -> 持有RequestMatcher对象 -> matches方法
//RequestMatcher接口用来支持匹配HttpServletRequest的简单策略。匹配策略有很多,此处不多介绍
//RequestMatcher -> AntPathRequestMatcher(实现),servlet通过url路径进行匹配
下图显示了 SecurityFilterChain 的角色。
SecurityFilterChain 中的 Filter 实例是在 FilterChainProxy 中进行注册的(而不是 DelegatingFilterProxy)。
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
使用 FilterChainProxy 作为起点的优势://这些特点很重要,仔细看
- 这样做为 Spring Security 的所有 Servlet 支持提供了一个起点,如果试图对 Spring Security 的 Servlet 支持进行故障排除,那么在 FilterChainProxy 中添加一个调试点是一个很好的开始。//所有的过滤器都在这里进行注册,然后开始调用,过滤器的调用从FilterChainProxy选择一个过滤器链开始
- FilterChainProxy 是 Spring Security 使用的核心,它可以执行一些不可选的任务。例如,清除 SecurityContext 以避免内存泄漏,应用 Spring Security 的HttpFirewall 来保护应用程序免受某些类型的攻击。//不可选的任务,就是必须做的一些事情或者一些通用的功能
- 在确定何时调用 SecurityFilterChain 方面提供了更大的灵活性。在 Servlet 容器中,仅根据 URL 调用 Filter 实例。然而,FilterChainProxy 可以通过使用 RequestMatcher 接口,基于 HttpServletRequest 中的任何内容来确定调用。//方便做选择,RequestMatcher -> 匹配策略接口
下图显示了多个 SecurityFilterChain 实例:
在 Multiple SecurityFilterChain 图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。
这里有一些匹配规则:
FilterChainProxy 只会调用匹配到的第一个 SecurityFilterChain。如请求 URL 为: /api/messages/,它首先匹配的是 SecurityFilterChain-0(匹配规则:/api/**),因此只调用 SecurityFilterChain-0,即使它也匹配 SecurityFilterChain-n(匹配规则:/**)。//执行第一个匹配到的过滤器链
如果请求 URL 为 /messages/ ,它与SecurityFilterChain-0(匹配规则:/api/**)不匹配,因此 FilterChainProxy 会继续尝试每个 SecurityFilterChain。假设没有其他 SecurityFilterChain 实例匹配,则调用 SecurityFilterChain-n。
需要注意的是,每个 SecurityFilterChain 都是唯一的,并且可以进行单独配置。如果应用程序希望 Spring security 忽略某些请求,那么可以在 SecurityFilterChain 中不设置任何的安全过滤器实例。//SecurityFilterChain 这个里边可以没有任何过滤器,那就是不进行任何拦截
5、Security Filters 安全过滤器实例
以下是 Spring Security 的过滤器排序的综合列表://按顺序排列的
- ForceEagerSessionCreationFilter
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter //异常处理过滤器
- FilterSecurityInterceptor
- SwitchUserFilter
//这些过滤器的排序是Spring官方提供的,后来Spring可能觉得这样展示的意义不大,删去了这部分内容, 补充了一些从日志查看过滤器加载顺序的说明。(过滤器的加载顺序会在Info日志中打印,以实时加载为准)。
6、Spring Security 如何处理安全异常?
//首先说,这里就是 Spring Security 的工作原理,重中之重,非常重要,虽然讲的是一个异常过滤器,但是实际上是一个执行流程。
ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。//两个异常,禁止访问 + 认证异常
ExceptionTranslationFilter 作为安全过滤器之一,自动插入到 FilterChainProxy 中。
下图显示了 ExceptionTranslationFilter 与其他组件的关系:
首先,ExceptionTranslationFilter 调用 FilterChain.doFilter(request, response) 来调用应用程序的剩余部分,这里有两种情况,用户未经过认证以及用户被拒绝访问。// Continue ProcessingRequest Normally -> 继续正常处理请求
如果用户未经过身份验证或者抛出了 AuthenticationException,则启动身份验证。
- 首先清除 SecurityContextHolder,这个是一个 Spring Security 中的一个核心类。
- RequestCache:保存 HttpServletRequest 请求,在身份验证成功后可以使用它重复原始请求。//也可以不保存,那么用户就需要重新去请求
- AuthenticationEntryPoint 用于从客户端获取请求凭据。例如,它可能重定向到登录页面或发送 WWW-Authenticate 报文头。//AuthenticationEntryPoint(接口) -> 比如重定向到登录页面,要求登录,执行从客户端获取请求凭据的策略
//以上这三个类都给出了身份验证的大致流程,流程中给出了这些关键类的使用时机,描述了 Spring Security 的大致框架。
如果是 AccessDeniedException,则拒绝访问。直接调用 AccessDeniedHandler 来处理拒绝访问。// AccessDeniedHandler(接口),也可以自定义处理策略
如果应用程序没有抛出 AccessDeniedException 或 AuthenticationException,则ExceptionTranslationFilter 不做任何事情。//这个过滤器只处理这两个异常
ExceptionTranslationFilter 的伪代码逻辑如下:
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) { //捕获特定异常
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication(); //启动认证流程
} else {
accessDenied(); //决绝策略
}
}
7、在认证的时候保存用户请求
当用户请求资源时,如果没有经过身份验证,就需要保存请求信息,当身份验证成功后再重新执行该请求。在 Spring Security 中,通过使用 RequestCache 的实现来保存 HttpServletRequest。//RequestCache是一个接口,请求缓存策略可以有不同的实现
RequestCacheAwareFilter 和 RequestCache 接口
HttpServletRequest 保存在 RequestCache 中。当用户成功通过身份验证时,将使用 RequestCache 重新执行原始请求。
RequestCacheAwareFilter 使用 RequestCache 来保存 HttpServletRequest。默认情况下,使用 HttpSessionRequestCache。
下面的代码演示了如何定制 RequestCache 的实现,该实现用于在参数 continue 存在的情况下检查保存请求的 HttpSession。//这里存储的是一个标记,通过RequestCacheAwareFilter过滤器使用
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue");
http
// ...
.requestCache((cache) -> cache.requestCache(requestCache));
return http.build();
}
如果不希望在会话中存储用户未经身份验证的请求,可以使用 NullRequestCache 实现。//不保存会话信息,直接重定向到登录页
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache.requestCache(nullRequestCache));
return http.build();
}
至此,全文结束。