文章目录
Spring Security 的 Serlvet支持是基于Servelt的过滤器实现的,下图展示了一个HTTP请求典型的分层处理过程。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/b20442dfa40071689c3c7bbf4b19e1f5.png)
当客户端发送一个请求到应用时,容器会创建一个过滤器链FilterChain,包含Filter和基于请求URI处理HttpServletRequest 的Servlet。在SpringMVC应用中,Servlet是DispatcherServlet的一个实例。一个Servlet最多只能处理一个HttpServletRequest和HttpServletResponse。
但是,可以使用多个过滤器来:
- 防止下游过滤器或Servlet被调用。在这种情况下,过滤器通常会写入HttpServletResponse。
- 修改下游过滤器和Servlet使用的HttpServletRequest或HttpServletResponse。
DelegatingFilterProxy
Spring提供了一个名叫DelegatingFilterProxy的Filter实现,来桥接Servlet容器的生命周期和Spring的上下文ApplicationContext。Servlet容器允许使用自己的标准注册过滤器,但它不知道Spring定义的bean。DelegatingFilterProxy可以通过标准的Servlet容器机制注册,将所有工作委托给实现过滤器的Springbean。
DelegatingFilterProxy从ApplicationContext中查找Bean Filter0,然后调用Bean Filter0。DelegatingFilterProxy的伪代码如下所示。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy
// delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
DelegatingFilterProxy的另一个好处是,它允许延迟查找过滤器bean实例。这一点很重要,因为容器需要在启动之前注册过滤器实例。然而,Spring通常使用ContextLoaderListener来加载Springbean,直到需要注册过滤器实例之后才会进行加载。
下面是DelegatingFilterProxy类的部分注释翻译:
web.xml通常包含一个DelegatingFilterProxy定义,指定的过滤器名称对应于Spring根应用程序上下文中的一个bean名称。然后,对过滤器代理的所有调用都将委托给Spring上下文中的bean,这是实现标准Servlet过滤器接口所必需的。
FilterChainProxy
Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxy是Spring Security提供的一个特殊过滤器,它允许通过SecurityFilterChain委托给多个过滤器实例。由于FilterChainProxy是一个Bean,它通常被包装在一个DelegatingFilterProxy中。
下面是FilterChainProxy类的部分注释翻译:
从3.1版开始,FilterChainProxy被配置SecurityFilterChain实例的列表,每个实例都包含一个{@link RequestMatcher}和一个用于匹配请求的过滤器列表。大多数应用程序只包含一个筛选器链,如果使用名称空间,则不必显式设置这些链。如果需要更细粒度的控制,可以使用的namespace属性。
“filter1”, “filter2”, "filter3"是声明在应用上下文中Filter的实例名, 名称的顺序定义了过滤器执行的顺序。如上图所示,可以将fitlers配置成null, 来将请求完全从Security过滤器链排除掉。尽可能将特定请求url放到最前面进行配置,当一个pattern被匹配成功后,FilterChainProxy不会向后遍历其他的FilterChain配置来确定是否有更匹配的pattern。遍历FilterChain获取代码如下:
private List<Filter> getFilters(HttpServletRequest request) {
for (SecurityFilterChain chain : filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
HttpFirewall
用于拒绝潜在危险的请求,该实现被注入FilterChainProxy,并在通过过滤器链发送任何请求之前被调用。如果响应行为也应该受到限制,它还可以提供一个响应包装器。我们看到HttpFirewall接口提供了两个抽象方法getFirewalledRequest、getFirewalledResponse分别用于校验请求和响应,默认使用StrictHttpFirewall类的实例,我们看看getFirewalledRequest方法中做了哪些事情:
- 先拒绝了请求URL上包含%25、%2e、%2f等特殊encode和decode编码的请求
- 其次再校验一个请求URL是否是正常的,不能包含类似"./", “/…/” 或 "/."这样的请求
- 最后通过比较URL每一位字符的ASCII码来排除有特殊字符的URL。
getFirewalledResponse默认不做任何处理,直接将请求返回前端。
SecurityFilterChain
FilterChainProxy使用SecurityFilterChain来确定应该为此请求调用哪些SpringSecurity的Filter。
SecurityFilterChain中的Security Filter通常是bean,但它们是在FilterChainProxy中注册的,而不是在DelegatingFilterProxy中注册的。FilterChainProxy为直接向Servlet容器注册或删除FilterProxy提供了许多优势。首先,它为Spring Security的所有Servlet支持提供了一个起点。因此,如果您试图解决Spring Security的Servlet支持问题,那么在FilterChainProxy中添加调试点是一个很好的起点。
SecurityFilter
SecurityFilter通过SecurityFilterChain的API插入FilterChainProxy。SecurityFilter的顺序是很重要的。下面是SecurityFilter的调用顺序列表:
- ChannelProcessingFilter
- WebAsyncManagerIntegrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CorsFilter
- CsrfFilter
- LogoutFilter
- OAuth2AuthorizationRequestRedirectFilter
- Saml2WebSsoAuthenticationRequestFilter
- X509AuthenticationFilter
- AbstractPreAuthenticatedProcessingFilter
- CasAuthenticationFilter
- OAuth2LoginAuthenticationFilter
- Saml2WebSsoAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- OpenIDAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- ConcurrentSessionFilter
- DigestAuthenticationFilter
- BearerTokenAuthenticationFilter
- BasicAuthenticationFilter
- RequestCacheAwareFilter
- SecurityContextHolderAwareRequestFilter
- JaasApiIntegrationFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- OAuth2AuthorizationCodeGrantFilter
- SessionManagementFilter
- ExceptionTranslationFilter
- FilterSecurityInterceptor
- SwitchUserFilter
总结一下:
DelegatingFilterProxy是一个Filter的实现,它将一个SpringBean注册到Servlet,这个SpringBean就是FilterChainProxy。FilterChainProxy持有一个SecurityFilterChain的列表,可以决定请求使用哪一套Security过滤器链,SecurityFilterChain又包含了多个SecurityFilter,对一个请求进行处理。
Handling Security Exceptions
ExceptionTranslationFilter允许将AccessDeniedException和AuthenticationException转换为HTTP响应。ExceptionTranslationFilter作为Security Filter之一插入FilterChainProxy。ExceptionTranslationFilter作为Security Filters之一插入FilterChainProxy。
- 首先,ExceptionTranslationFilter调用FilterChain.doFilter()来调用应用程序的其余部分。
- 如果用户未经身份验证或是身份验证异常,则启动身份验证。
(1) 清空当前请求保存在SecurityContextHolder的身份信息。
(2) 将当前请求保存在RequestCache中。当用户成功进行身份验证时,RequestCache用于重播原始请求。
(3) AuthenticationEntryPoint用于身份认证失败时,启动身份校验例如重定向登录页、返回401等等、发送WWW认证头。在调用这个方法前,ExceptionTranslationFilter将请求的target URL填充到名为AbstractAuthenticationProcessingFilter.SPRING_SECURITY_SAVED_REQUEST_KEY的HttpSession属性。 - 否则,如果是AccessDeniedException,则拒绝访问。将调用AccessDeniedHandler来处理拒绝的访问。
- 如果应用程序不抛出AccessDeniedException或AuthenticationException,ExceptionTranslationFilter不做任何事。