本文基于 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 服务器启动时初始化。

  • 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 中包含下述过滤器(按执行顺序):

  • 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

Spring Security 中的过滤器的更多相关文章

  1. Spring Security 实战干货:图解Spring Security中的Servlet过滤器体系

    1. 前言 我在Spring Security 实战干货:内置 Filter 全解析对Spring Security的内置过滤器进行了罗列,但是Spring Security真正的过滤器体系才是我们了 ...

  2. spring security 11种过滤器介绍

    1.HttpSessionContextIntegrationFilter 位于过滤器顶端,第一个起作用的过滤器. 用途一,在执行其他过滤器之前,率先判断用户的session中是否已经存在一个Secu ...

  3. Spring Security(2):过滤器链(filter chain)的介绍

    上一节中,主要讲了Spring Security认证和授权的核心组件及核心方法.但是,什么时候调用这些方法呢?答案就是Filter和AOP.Spring Security在我们进行用户认证以及授予权限 ...

  4. Spring Security(四) —— 核心过滤器源码分析

    摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...

  5. 六:Spring Security 中使用 JWT

    Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...

  6. Spring Security:Servlet 过滤器(三)

    3)Servlet 过滤器 Spring Security 过滤器链是一个非常复杂且灵活的引擎.Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先了解过 ...

  7. Spring Security配置个过滤器也这么卷

    以前胖哥带大家用Spring Security过滤器实现了验证码认证,今天我们来改良一下验证码认证的配置方式,更符合Spring Security的设计风格,也更加内卷. CaptchaAuthent ...

  8. [收藏]Spring Security中的ACL

    ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...

  9. Spring Security中html页面设置hasRole无效的问题

    Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...

随机推荐

  1. 【Tomcat】性能优化

    一.JVM优化 1.内存优化. 2.垃圾回收策略优化. 二.server.xml的connector优化(connector是与HTTP请求处理相关的容器,三个容器的初始化顺序为:Server-> ...

  2. Integer to Boolean strange syntax

    Question: I'm less than a year into C++ development (focused on other languages prior to this) and I ...

  3. CSS如何让不相等的字符上下对齐

    最后效果: <div class="main"> <span style="font-size:12px;"><dl class= ...

  4. 免费工资总额管控系统-JXHR2016

    •工资总额是指按照国家统计局规定的统计口径或企业规定,在一定时期内支付给各类用工的劳动报酬总额 •工资总额,即基本工资,包括岗位工资.各项津补贴 •JXHR2016以薪酬管控为核心,结合人力资源规划. ...

  5. 安卓开发_WebView如何在Fragment中使用

    之前学习了如何在activity中使用WebView控件来显示网页. 在我的实际开发中,有需要在Fragment中用到WebView控件的,那么就百度学习了一下 其实很简单,但是当然不是和在Activ ...

  6. 《Inside C#》笔记(完) 程序集

    程序集内部包含了各种相关的模块.资源文件.配置文件等,将这些在功能上相关的文件整合到单个文件中,以便于部署和维护.使用C#编译器编译程序时,生成的便是程序集. 一.清单数据 a)如果编译的是独立应用程 ...

  7. 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

    前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙——HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...

  8. Scala学习笔记2 (带着问题学习, 逐渐扩展。理解吃透scala.)

    问题: 把 文本字符串"[1, 2, 3, 4, 5]" 转换成一个数组. 答案: val x = "[1, 2, 3, 4, 5]" val y =x sli ...

  9. 字典Key值为变量

    m='aaa4a' d = dict(name=m) print d['name']

  10. 一文读懂遗传算法工作原理(附Python实现)

    选自AnalyticsVidhya 参与:晏奇.黄小天 近日,Analyticsvidhya 上发表了一篇题为<Introduction to Genetic Algorithm & t ...