Spring Security 中的过滤器

本文基于 spring-security-core-5.1.1 和 tomcat-embed-core-9.0.12。

Spring Security 的本质是一个过滤器链(filter chain),当一个请求(request)访问 Web 应用提供的资源时,首先要经过一系列过滤器(filter)的处理,根据过滤器处理的结果返回不同的信息,包括:

  • 返回用户请求的资源
  • 请求未认证,需要认证

    包括请求未认证和之前的认证已过期,一般会重定向到一个登录页面让用户认证。

  • 已认证请求没有权限访问资源

Spring Web 中的过滤器链

用户请求进入 Spring Web 的入口是 ApplicationFilterChain(org.apache.catalina.core 包)的 doFilter() 方法,在这条过滤器链上(按执行顺序)有下述过滤器。这些过滤器在 Tomcat 的 web.xml 文件中定义,并在 Web 服务器启动时初始化。
Spring Security 中的过滤器

  • OrderedCharacterEncodingFilter

    继承自 CharacterEncodingFilter,实现 OrderedFilter 接口来指定过滤器执行的顺序。

    用于设置请求(和响应)的编码。

  • OrderedHiddenHttpMethodFilter

    本过滤器会将请求的方法参数(method parameters)转换为 HTTP method,使得可通过 HttpServletRequest 的 getMethod() 方法获取。

    由于浏览器目前仅支持 GET 和 POST 请求,一个常用的技术是使用一个 POST 请求再加上一些隐藏的表单字段(hidden form field)来表示 PUT、DELETE 和 PATCH 请求。

    由于需要检查 POST body 参数,因此在 multipart POST 请求时,本过滤器需要在 multipart 处理之后运行。

    具体的做法是在过滤器链中将 MultipartFilter 放在 HiddenHttpMethodFilter 前面。

  • OrderedFormContentFilter

    为 PUT、PATCH 以及 DELETE 请求解析表单数据(form data),并将解析结果暴露(exposes)为 Servlet request 参数。

    Servlet 规范默认只有 POST 请求会使用本过滤器。

  • OrderedRequestContextFilter

    通过 LocaleContextHolder 和 RequestContextHolder 将当前 request 暴露给当前线程。

    RequestContextListener 和 DispatcherServlet 也会将相同的 request 上下文暴露给当前线程。

    本过滤器主要用于第三方 Servlets,Spring 自己的 DispatcherServlet 处理效率很高,不需要使用到它。

  • DelegatingFilterProxy(DelegatingFilterProxyRegistrationBean$1)

    DelegatingFilterProxyRegistrationBean 在 Web 应用启动时通过 getFilter() 将 DelegatingFilterProxy 注册到 Servlet 容器。

    DelegatingFilterProxy 是 Servlet 过滤器的代理,它将过滤器委托给实现了 Filter 接口的 Spring Bean。

    web.xml 文件中通常会包含一个指定 filter-name 的 DelegatingFilterProxy 定义,这个 filter-name 即是 Spring 容器中的 bean-name。

    对过滤器代理的调用会被委托给 Spring 容器中的 bean。

  • SsoSecurityInterceptorImpl

    用户自定义实现的过滤器。

  • WsFilter

    用于处理 WebSocket 连接时的初始 HTTP 连接,位于 org.apache.tomcat.websocket.server 包。

Spring Security 中的过滤器链

Spring Security 对请求的处理从 DelegatingFilterProxy 的 doFilter() 方法开始,并包括用户自定义的过滤器(若存在)。DelegatingFilterProxy 在 doFilter() 中通过 FilterChainProxy 定义的过滤器链来处理请求。

FilterChainProxy 中过滤器链上的 filter 都在 org.springframework.security 包中(除去用户自定义的过滤器)。

FilterChainProxy 负责将 Filter 请求分发(delegates)到 Spring 容器的 filter beans 列表,由列表中的 filter bean 对请求进行认证和授权相关的处理。

通过在 web.xml 文件中添加 DelegatingFilterProxy 的声明,FilterChainProxy 被连接到了 servlet 容器的过滤器链中。

FilterChainProxy 中包含下述过滤器(按执行顺序):
Spring Security 中的过滤器

  • WebAsyncManagerIntegrationFilter

    提供 SecurityContext 和 WebAsyncManager 的集成,实现对请求处理的异步管理。

  • SecurityContextPersistenceFilter

    本过滤器是容器 session 和 Spring Security 之间的桥梁,它应该在任何认证处理机制(authentication processing mechanisms)之前执行,因为 authentication 需要一个可用的 SecurityContext。

    它会从 session 中取出 key 为 SPRING_SECURITY_CONTEXT 的 attribute,这是一个 SecurityContextImpl 实例。

    若请求在之前已认证,那么 session 会关联上相关信息。若没有,那么通常会经过 AnonymousAuthenticationFilter,此时其 Authentication 值是 AnonymousAuthenticationToken。

    由于请求是单独的线程,而安全认证是基于 session 的,所以请求需要将认证信息从 session 中取出,认证结束后再复制回 session(因为认证信息可能会发送改变) 以供下一个请求使用。

    在 AbstractAuthenticationProcessingFilter 的 successfulAuthentication() 中,认证成功后会将 Authentication 写到 SecurityContext 中。

    在 doFilter() 方法中会获取 request 的 __spring_security_scpf_applied 属性,若存在该属性则把请求传递给下一个过滤器。否则设置该属性,并继续执行 doFilter() 方法。

    判断 forceEagerSessionCreation 属性,查看是新创建了 session。

    获取一个 SecurityContext,在执行过滤器链前把它放入 SecurityContextHolder 中,完成过滤器链的执行后在将其从 SecurityContextHolder 中清除。

  • HeadWriterFilter

    本过滤器用来向当前响应添加 headers,这对添加某些启用浏览器保护的 headers 可能会很有用。

    例如 X-Frame-Options、X-XSS-Protection 以及 X-Content-Type-Options。

    通过在 doFilterInternal() 方法中调用 HeaderWriterResponse 的 writeHeaders() 方法来向 response 中写入 headers。

    在 writeHeaders() 方法中会调用 OnCommittedResponseWrapper 的 isDisableOnResponseCommitted() 来判断是否需要向 response 中写入 headers。

  • CsrfFilter

    本过滤器使用了一个同步器令牌模式(synchronizer token pattern)来实现 CSRF保护。

    开发者应该确保对每个允许状态改变(allows state to change)的请求都调用了本过滤器,即应确保 Web 应用遵循了合适的 REST 语义(GET、HEAD、TRACE 和 OPTIONS 等方法不应有状态改变)。

    通常,CsrfTokenRepository 实现选择使用由 LazyCsrfTokenRepository 包装的 HttpSessionCsrfTokenRepository 在 HttpSession 中存储 CsrfToken。

    它优先(preferred to)将令牌存储在 cookie 中,该 cookie 可由客户端应用修改。

    在它的 doFilterInternal() 方法中使用了一个 RequestMatcher 来判断当前请求是否匹配 CSRF 的处理。

    默认的方式是忽略 GET、HEAD、TRACE 和 OPTIONS 请求,处理其他所有请求。

    若要禁用 CSRF,则需要在 WebSecurityConfigurerAdapter 的 configure(HttpSecurity http) 方法中调用 http.csrf().disable()。

  • LogoutFilter

    本过滤器用于处理来自 /logout 的请求,注销一个用户凭证(principal)。

    注销(logout)后会重定向到一个指定的 URL,这个 URL 由 LogoutSuccessHandler 或 logoutSuccessUrl(取决于使用的构造器)来决定。

    它会轮询一系列 LogoutHandler,处理器(handlers)应该按它们被需要的顺序指定。通常情况下需要调用 TokenBasedRememberMeServices 和 SecurityContextLogoutHandler。

    通过在 doFilter() 方法中调用 requiresLogout() 方法来判断当前请求是否需要注销。

    使用一个 RequestMatcher 来判断当前请求的 URL 是否匹配 /logout。

  • UsernamePasswordAuthenticationFilter

    本过滤器用于处理来自 /login 的请求,完成对用户请求认证的处理。它继承自 AbstractAuthenticationProcessingFilter 类。

    用户提交的身份验证表单(authentication form)需要提供 username 和 password 两个参数。

    参数名 username 和 password 可通过设置 usernameParameter 和 passwordParameter 属性来修改。

    在 AbstractAuthenticationProcessingFilter 的 doFilter() 方法中会调用 requiresAuthentication() 方法来判断当前请求是否需要认证。

    通过一个 RequestMatcher 来匹配当前请求的 URL 和 method。

  • ConcurrentSessionFilter

    本过滤器用于处理每个请求中的 session。

    若 session 未过期,在 doFilter() 方法中调用 SessionRegistry 的 refreshLastRequest() 方法来使 session 中数据总是最新。

    若 session 过期,在 doFilter() 方法中调用该过滤器的 doLogout() 方法,该方法会调用已配置的 logout handlers。

  • RequestCacheAwareFilter

    doFilter() 方法中调用 RequestCache 的 getMatchingRequest() 方法来查找当前请求是否已被缓存。

    若当前请求已被缓存,则使用重新构建(reconstituting)的请求替换当前请求。

  • SecurityContextHolderAwareReqeustFilter

    本过滤器使用一个实现了 servlet API 安全方法的请求包装器(request wrapper)包装 ServletRequest,使其具有更丰富的接口。

    这通过在 doFilter() 方法中调用 HttpServletRequestFactory 接口的 create() 方法来完成。

    用户请求在本过滤器中与 SecurityContext 关联。

  • RememberMeAuthenticationFilter

    在用户请求没有认证而直接访问资源时,本过滤器会查找请求中的 remember-me 信息并进一步处理。

    在 doFilter() 方法中,从 SecurityContext 中获取 Authentication,若 Authentication 不为 null,表示请求已认证,把请求传递给下一个过滤器,否则执行 remember-me 相关的处理。

    通过 RememberMeServices 的 autoLogin() 方法获取一个 Authentication,若 Authentication 为 null,表示没有 remember-me 信息,是一个匿名请求。然后把请求传递给下一个过滤器(AnonymousAuthenticationFilter)。

    若 Authentication 不为 null,则将其存入 SecurityContext,然后调用本过滤器的 onSuccessfulAuthentication() 方法进行后续处理。

  • AnonymousAuthenticationFilter

    本过滤器用于为匿名访问的用户建立匿名认证。

    doFilter() 方法会从 SecurityContext 中获取一个 Authentication,若该 Authentication 不为 null,表示已有一个匿名认证,然后把请求传递给下一个过滤器。

    若 Authentication 为 null 则调用 createAuthentication() 方法创建一个匿名认证并放入 SecurityContext 中。

  • SessionManagementFilter

    本过滤器负责 session 相关的行为,它会检查当前请求是否存在于 SecurityContextRepository 中并判断是否已认证。

    若已认证,则调用 SessionAuthenticationStrategy 的相关方法来执行任何 session 相关的活动,如激活会话固定保护机制(session-fixation protection mechanisms)或检查多个并发登录等。

  • ExceptionTranslationFilter

    本过滤器用于处理过滤器链中抛出的任何 AccessDeniedException 和 AuthenticationException 异常,它是 Java 异常和 HTTP 响应之间的桥梁。

  • SsoSecurityInterceptorImpl

    用户自定义的过滤器。

  • FilterSecurityInterceptor

    执行 HTTP 资源的安全性处理。

Spring Security 中的其他过滤器

除了前面分析到的过滤器外,Spring Security 还包含一些其他的过滤器,比如下面两个过滤器用于默认生成 login 和 logout 页面,这些过滤器等遇到的时候再具体分析。

  • DefaultLoginPageGeneratingFilter
  • DefaultLogoutPageGeneratingFilter
上一篇:asp.net的Request.ServerVariables参数说明


下一篇:ECStore在Win环境下如何运行CMD命令