Spring Security 架构

 2023-02-08
原文作者:公子奇 原文地址:https://juejin.cn/post/7095516572462088206

本文我们讨论Spring Security在基于Servlet的应用程序中的高级体系结构。我们将在参考资料的身份验证授权保护漏洞部分中构建对这种高级理解。

过滤器综述

Spring Security的Servlet支持是基于Servlet过滤器的,所以通常先看看过滤器的角色是有帮助的。下图显示了单个HTTP请求处理程序的典型分层。

202301012021353541.png

客户端向应用程序发送一个请求,容器创建一个FilterChain,其中包含Filter[过滤器]Servlet,它们应该根据请求URI的路径处理HttpServletRequest。在Spring MVC应用程序中,ServletDispatcherServlet的一个实例。最多一个Servlet可以处理单个HttpServletRequestHttpServletResponse。但是,可以使用多个Filter:

  • 阻止调用下游Filter[过滤器]Servlet。在这个实例中,Filter[过滤器]通常会写入HttpServletResponse
  • 修改下游过滤器Servlet使用的HttpServletRequestHttpServletResponse

Filter的强大功能来自传递给它的FilterChain

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // 在应用程序的其余部分之前执行某些操作
        chain.doFilter(request, response); // 调用应用程序的其余部分
        // 在应用程序的其余部分之后做些什么
    }

因为一个Filter只影响下游的FiltersServlet,所以每个Filter被调用的顺序是非常重要的。

DelegatingFilterProxy

Spring提供了一个名为DelegatingFilterProxy的过滤器实现,它允许在Servlet容器的生命周期和Spring的ApplicationContext之间建立桥梁。Servlet容器允许使用自己的标准注册过滤器,但它不知道Spring定义的Bean。DelegatingFilterProxy可以通过标准的Servlet容器机制注册,但可以将所有工作委托给实现Filter的Spring Bean。

202301012021359082.png

下面是DelegatingFilterProxy如何适应Filter[过滤器]和FilterChain的图片。

202301012021363773.png

DelegatingFilterProxyApplicationContext中查找Bean Filter0,然后调用Bean Filter0DelegatingFilterProxy的伪代码如下所示。

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
        // 惰性地获取注册为Spring Bean的Filter
        // 例如DelegatingFilterProxy委托是一个Bean Filter0
        Filter delegate = getFilterBean(someBeanName);
        // 将工作委托给Spring Bean
        delegate.doFilter(request, response);
    }

DelegatingFilterProxy的另一个好处是,它允许延迟查看Filter Bean实例。这很重要,因为容器在启动之前需要注册Filter实例。但是,Spring通常使用ContextLoaderListener来加载Spring Bean,直到需要注册Filter实例之后才会加载。

FilterChainProxy

Spring Security的Servlet支持包含在FilterChainProxy中。FilterChainProxy是Spring Security提供的一个特殊的Filter,它允许通过SecurityFilterChain委托给多个Filter实例。因为FilterChainProxy是一个Bean,它通常被包装在DelegatingFilterProxy中。

202301012021369174.png

SecurityFilterChain

SecurityFilterChainFilterChainProxy用来确定这个请求应该调用哪个Spring Security过滤器。

202301012021376075.png

SecurityFilterChain中的Security过滤器是典型的Bean,但它们是用FilterChainProxy而不是DelegatingFilterProxy注册的。FilterChainProxy为直接注册Servlet容器或DelegatingFilterProxy提供了许多优势。首先,它为Spring Security的所有Servlet支持提供了一个起点。由于这个原因,如果你试图排除Spring Security的Servlet支持的故障,在FilterChainProxy中添加一个调试点是一个很好的开始。

第二,由于FilterChainProxy是Spring Security使用的中心,它可以执行非可选的任务。例如,它清除SecurityContext以避免内存泄漏。它还应用Spring Security的HttpFirewall来保护应用程序免受某些类型的攻击。

此外,它在决定何时应该调用SecurityFilterChain方面提供了更大的灵活性。在Servlet容器中,仅基于URL调用Filters。然而,FilterChainProxy可以基于HttpServletRequest中的任何东西来确定调用,通过利用RequestMatcher接口。

事实上,FilterChainProxy可以用来确定应该使用哪个SecurityFilterChain。这允许为应用程序的不同部分提供完全独立的配置。

202301012021380856.png

在多个SecurityFilterChain图中,FilterChainProxy决定应该使用哪个SecurityFilterChain。只有第一个匹配的SecurityFilterChain才会被调用。如果一个/api/messages/的URL被请求,它会首先匹配SecurityFilterChain0/api/**模式,所以只有SecurityFilterChain0会被调用,即使它也匹配SecurityFilterChainn。如果请求的URL是/messages/,它将不会匹配SecurityFilterChain0/api/**模式,因此FilterChainProxy将继续尝试每个SecurityFilterChain。假设没有其他实例,SecurityFilterChain实例匹配SecurityFilterChainn将被调用。

注意,SecurityFilterChain0只配置了三个Security Filter实例。然而,SecurityFilterChainn配置了四个Security Filter。需要注意的是,每个SecurityFilterChain都可以是惟一的,并且可以单独配置。事实上,如果应用程序希望Spring Security忽略某些请求,SecurityFilterChain可能没有安全过滤器。

Security 过滤器

Security Filters通过SecurityFilterChain API插入到FilterChainProxy中。过滤器的顺序很重要。通常不需要知道Spring Security的过滤器的顺序。然而,有时候知道顺序是有益的。

下面是Spring Security Filter排序的综合列表:

  • 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

处理Security异常

ExceptionTranslationFilter允许将AccessDeniedExceptionAuthenticationException转换为HTTP响应。

ExceptionTranslationFilter作为一个Security过滤器被插入到FilterChainProxy中。

202301012021388677.png

  • 首先,ExceptionTranslationFilter调用FilterChain.doFilter(request、response)来调用应用程序的其余部分。

  • 如果用户没有通过身份验证,或者是AuthenticationException,则启动身份验证。

    • 清除SecurityContextHolder
    • HttpServletRequest被保存在RequestCache中。当用户成功通过身份验证时,使用RequestCache重现原始请求。
    • AuthenticationEntryPoint用于从客户端请求凭据。例如,它可能重定向到登录页面或发送WWW-Authenticate报头。
  • 否则,如果是AccessDeniedException,则AccessDenied。调用AccessDeniedHandler来处理被拒绝的访问。

Note : 如果应用程序没有抛出AccessDeniedExceptionAuthenticationException,则ExceptionTranslationFilter不会做任何事情。

ExceptionTranslationFilter的伪代码看起来像这样:

    try {
        filterChain.doFilter(request, response);
    } catch (AccessDeniedException | AuthenticationException ex) {
        if (!authenticated || ex instanceof AuthenticationException) {
            startAuthentication(); //启动身份验证
        } else {
            accessDenied(); //拒绝访问
        }
    }