回顾 Filters
Spring Security 的 Servlet 支持是基于 Severlet 过滤器的,因此通常先看看过滤器的角色是有帮助的。下面的图片显示了单个 HTTP 请求的处理程序的典型分层。
客户机向应用程序发送一个请求,容器创建一个 FilterChain(过滤器链),其中包含过滤器和Servlet,这些过滤器和Servlet应根据请求URI的路径处理HttpServletRequest。一个Servlet可以处理单个HttpServletRequest和HttpServletResponse。但是,可以使用多个过滤器来执行以下操作:
- 阻止后面的 Filter 或 Servlet 被调用,一般会通过 HttpServletResponse 直接完成响应。
- 修改后面的 Filter 和Servlet 使用的 HttpServletRequest 或 HttpServletResponse。
过滤器的功能来自传递给它的过滤器链(FilterChain )
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// 在请求到达后面Filter和Servlet之前做点什么
chain.doFilter(request, response); // invoke the rest of the application
// 在后面Filter和Servlet响应之后做点什么
}
由于 Filter 只影响后面 Filter 和 Servlet,因此每个 Filter 被调用的顺序非常重要。
DelegatingFilterProxy
Spring 提供了一个名为 DelegatingFilterProxy 的 Filter 实现,允许在 Servlet 容器的生命周期和 Spring 的 ApplicationContext 之间建立桥接。Servlet 容器允许使用自己的标准注册过滤器,但它不知道 Spring 定义的 Beans。可以通过标准的 Servlet 容器机制注册 DelegatingFilterProxy,但将所有工作委托给一个实现 Filter 的 Spring Bean。
下图描述了 DelegatingFilterProxy 如何适用于 Filters 和 FilterChain 的:
DelegatingFilterProxy 伪代码:
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
// 延迟获取注册为Spring Bean的Filter
val delegate: Filter = getFilterBean(someBeanName)
// 将工作委派给Spring Bean
delegate.doFilter(request, response)
}
DelegatingFilterProxy 的另一个好处是,它允许延迟查看 Filter bean 实例。这很重要,因为容器需要在容器启动之前注册 Filter 实例。然而,Spring 通常使用一个 ContextLoaderListener 来加载 Spring Beans,这在 Filter 实例需要注册之前不会执行。
FilterChainProxy
Spring Security 的 Servlet 支持包含在 FilterChainProxy 中。FilterChainProxy 是 Spring Security 提供的一个特殊的 Filter,它允许通过 SecurityFilterChain 委托给许多 Filter 实例。因为 FilterChainProxy 是一个 Bean,所以它通常包装在 DelegatingFilterProxy 中。
SecurityFilterChain
FilterChainProxy 使用 SecurityFilterChain 来确定应该为此请求调用哪个 Spring 安全过滤器。
SecurityFilterChain中的安全筛选器通常是Bean,但它们注册到FilterChainProxy而不是DelegatingFilterProxy。
FilterChainProxy为直接向Servlet容器或DelegatingFilterProxy注册提供了许多优势。
首先,它为所有Spring Security的Servlet支持提供了一个起点。因此,如果您试图排除Spring Security的Servlet支持的故障,在FilterChainProxy中添加调试点是一个很好的起点。
其次,由于 FilterChainProxy 是 Spring Security 使用的核心,它可以执行不被视为可选的任务。例如,它清除 SecurityContext 以避免内存泄漏。它还应用 Spring Security 的 HttpFirewall 来保护应用程序免受某些类型的攻击。
此外,它在确定何时应该调用 SecurityFilterChain 方面提供了更大的灵活性。在 Servlet 容器中,过滤器仅根据 URL 调用。但是,FilterChainProxy 可以利用 RequestMatcher 接口根据 HttpServletRequest 中的任何内容来确定调用。
事实上,可以使用 FilterChainProxy 来确定应该使用哪个 SecurityFilterChain。这允许为应用程序的不同部分提供完全独立的配置。
在上图中,FilterChainProxy 决定应该使用哪个 SecurityFilterChain。只有匹配的第一个 SecurityFilterChain 将被调用。如果请求/api/messages/的 URL,它将首先匹配 securityFilterChain0的/api/** 模式,因此只会调用 SecurityFilterChain0,即使它也匹配 SecurityFilterChainn。如果请求/messages/的 URL,它将不匹配 SecurityFilterChain0的/api/** 模式,因此 FilterChainProxy 将继续尝试每个 SecurityFilterChain。假设没有其他的 SecurityFilterChain 实例匹配,SecurityFilterChainn 将被调用。
注意 SecurityFilterChain0 只配置了三个安全过滤器实例。但是,SecurityFilterChainn 配置了四个安全过滤器。需要注意的是,每个 SecurityFilterChain 可以是唯一的,并且可以隔离配置。实际上,如果应用程序希望 Spring Security 忽略某些请求,SecurityFilterChain 可能具有零安全筛选器。
Security Filters
安全过滤器通过 SecurityFilterChain API 插入到 FilterChainProxy 中。过滤器的顺序很重要。通常不需要知道 Spring Security 的过滤器的顺序。然而,有时候知道顺序是有益的。
以下是 Spring Security Filter 排序的综合列表:
Filter | 说明 |
---|---|
ChannelProcessingFilter | |
WebAsyncManagerIntegrationFilter | 将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成 |
SecurityContextPersistenceFilter | 在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将 SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将 SecurityContextHolder 中的信息清除,例如在Session中维护一个用户的安全信息就是这个过滤器处理的。 |
HeaderWriterFilter | 用于将头信息加入响应中 |
CorsFilter | 用于处理跨站请求伪造 |
LogoutFilter | 用于处理退出登录 |
OAuth2AuthorizationRequestRedirectFilter | |
Saml2WebSsoAuthenticationRequestFilter | |
UsernamePasswordAuthenticationFilter | 用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的usernameParameter 和 passwordParameter 两个参数的值进行修改 |
OpenIDAuthenticationFilter | |
DefaultLoginPageGeneratingFilter | 如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面 |
DefaultLogoutPageGeneratingFilter | |
DigestAuthenticationFilter | |
BearerTokenAuthenticationFilter | |
BasicAuthenticationFilter | 检测和处理 http basic 认证 |
RequestCacheAwareFilter | 用来处理请求的缓存 |
SecurityContextHolderAwareRequestFilter | 主要是包装请求对象request |
JaasApiIntegrationFilter | |
RememberMeAuthenticationFilter | 当用户没有登录而直接访问资源时, 从 cookie 里找出用户的信息, 如果 Spring Security 能够识别出用户提供的remember me cookie, 用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。 |
AnonymousAuthenticationFilter | 检测 SecurityContextHolder 中是否存在 Authentication 对象,如果不存在为其提供一个匿名 Authentication。 |
OAuth2AuthorizationCodeGrantFilter | |
SessionManagementFilter | 管理 session 的过滤器 |
ExceptionTranslationFilter | 处理 AccessDeniedException 和 AuthenticationException 异常 |
FilterSecurityInterceptor | 可以看做过滤器链的出口 |
SwitchUserFilter |
异常处理
ExceptionTranslationFilter 允许将 AccessDeniedException 和 AuthenticationException 转换为 HTTP 响应。
ExceptionTranslationFilter 作为安全过滤器之一插入到 FilterChainProxy 中。
- ① 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter (request, response) 来调用应用程序的其余部分。
- ② 如果用户没有经过身份验证,或者它是 AuthenticationException,那么启动身份验证。
- SecurityContextHolder 被清空了
- HttpServletRequest 保存在 RequestCache 中。当用户成功进行身份验证时,使用 RequestCache 重播原始请求。
- AuthenticationEntryPoint 用于从客户机请求凭据。例如,它可能会重定向到一个登录页面,或者发送一个 WWW-Authenticate 标头。
- ③ 否则,如果它是AccessDeniedException,则访问被拒绝。调用AccessDeniedHandler来处理拒绝访问。
如果应用程序没有抛出 AccessDeniedException 或 AuthenticationException,那么 ExceptionTranslationFilter 不会做任何事情。
ExceptionTranslationFilter 的伪代码:
try {
//调用FilterChain.doFilter(request, response)等同于调用应用程序的其余部分。
// 这意味着,如果应用程序的另一部分(即FilterSecurityInterceptor或方法安全性)抛出了AuthationException或AccessDeniedException,它将在此处被捕获和处理。
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
// 如果用户未经过身份验证,或者它是一个身份验证异常,则启动身份验证
startAuthentication();
} else {
// 否则,访问被拒绝
accessDenied();
}
}