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 服务器启动时初始化。
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 中的过滤器的更多相关文章
- Spring Security 实战干货:图解Spring Security中的Servlet过滤器体系
1. 前言 我在Spring Security 实战干货:内置 Filter 全解析对Spring Security的内置过滤器进行了罗列,但是Spring Security真正的过滤器体系才是我们了 ...
- spring security 11种过滤器介绍
1.HttpSessionContextIntegrationFilter 位于过滤器顶端,第一个起作用的过滤器. 用途一,在执行其他过滤器之前,率先判断用户的session中是否已经存在一个Secu ...
- Spring Security(2):过滤器链(filter chain)的介绍
上一节中,主要讲了Spring Security认证和授权的核心组件及核心方法.但是,什么时候调用这些方法呢?答案就是Filter和AOP.Spring Security在我们进行用户认证以及授予权限 ...
- Spring Security(四) —— 核心过滤器源码分析
摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...
- 六:Spring Security 中使用 JWT
Spring Security 中使用 JWT 1.无状态登录 1.1 什么是有状态? 1.2 什么是无状态 1.3 如何实现无状态 2.JWT 2.1 JWT数据格式 2.2 JWT交互流程 2.3 ...
- Spring Security:Servlet 过滤器(三)
3)Servlet 过滤器 Spring Security 过滤器链是一个非常复杂且灵活的引擎.Spring Security 的 Servlet 支持基于 Servlet 过滤器,因此通常首先了解过 ...
- Spring Security配置个过滤器也这么卷
以前胖哥带大家用Spring Security过滤器实现了验证码认证,今天我们来改良一下验证码认证的配置方式,更符合Spring Security的设计风格,也更加内卷. CaptchaAuthent ...
- [收藏]Spring Security中的ACL
ACL即访问控制列表(Access Controller List),它是用来做细粒度权限控制所用的一种权限模型.对ACL最简单的描述就是两个业务员,每个人只能查看操作自己签的合同,而不能看到对方的合 ...
- Spring Security中html页面设置hasRole无效的问题
Spring Security中html页面设置hasRole无效的问题 一.前言 学了几天的spring Security,偶然发现的hasRole和hasAnyAuthority的区别.当然,可能 ...
随机推荐
- elasticsearch6.7 05. Document APIs(7)Update By Query API
6.Update By Query API _update_by_query 接口可以在不改变 source 的情况下对 index 中的每个文档进行更新.这对于获取新属性或其他联机映射更改很有用.以 ...
- Maven的pom.xml文件详解【转载】
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/20 ...
- Linux常用基本命令:三剑客命令之-awk内置函数用法
awk的内置函数大致可以分类为算数函数.字符串函数.时间函数.其他函数等 算数函数 最常用的算数函数有rand函数.srand函数.int函数. 可以使用rand函数生成随机数,但是使用rand函数时 ...
- lfs(systemd版本)学习笔记-第2页
我的邮箱地址:zytrenren@163.com欢迎大家交流学习纠错! lfs(systemd)学习笔记-第1页 的地址:https://www.cnblogs.com/renren-study-no ...
- Python 练习: 计算器
import re def format_string(s): # 对表达式进行格式化 s = s.replace(' ', '') s = s.replace("--", &qu ...
- window的cmd命令行下新增/删除文件夹及文件
新增文件夹 (md / mkdir) md <folderName>: folderName 就是文件路径,只输入文件夹名称时表示在当前目录下创建文件夹. 比如:md F:\test\pr ...
- CSS3属性-webkit-font-smoothing字体抗锯齿渲染
对字体进行抗锯齿渲染可以使字体看起来会更清晰舒服.在图标字体成为一种趋势的今天,抗锯齿渲染使用也越来越多. font-smoothing是非标准的CSS定义.它被列入标准规范的草案中,后由于某些原因从 ...
- ajax小知识
1.ajax发送get请求时,需要注意如下情况: var uri="http://127.0.0.1:8071/springmvcdemo/bigdataapi/publishdata&qu ...
- 基于InfluxDB实现分页查询功能
InfluxDB作为时序数据库中的翘楚,应用范围非常广泛,尤其在监控领域. 最近做了一个功能,将InfluxDB中的数据查询出来后,在前台分页展现,比如每页10条,一共100页,可以查看首页.末页,进 ...
- engineercms支持文档协作和文档流程,基于flow
我们用于管理文件的系统,比如网盘云盘等,并不具备流程功能,所谓流程,本质是修改文档状态,比如,从初始状态,不同权限的人登录,查看这个文件,具有修改这个文档状态的权限,比如将初始状态修改为已审查状态. ...