总文档 :文章目录
Github : github.com/black-ant
一 . 前言
上一篇聊了聊 Secutity 的基础 , 这一篇我们聊一聊 Securiy Filter , 基本上 Security 常见得功能都能通过 Filter 找到相关的痕迹 .
二 . 一个基本的 Filter 案例
先来看看我们之前这么注册 Filter 的 >>>
1 | java复制代码AbstractAuthenticationProcessingFilter filter = new DatabaseAuthenticationFilter(); |
我们来追溯一下 Filter 怎么被加载进去的 :
Step First : 将 Filter 加载到 Security 体系中
1 | java复制代码// 添加到 HttpSecurity |
HttpSecurity 这个类我们后面会详细说说 , 这里了解到他其中维护了一个 Filter 集合即可 , 这个集合会被加载到 FilterChain 中
Step 2 : Filter Chain 的使用方式
1 | java复制代码// 成功标注断点后 , 可以追溯到整个的加载流程 : |
可以看到 , 最开始添加到 Filter 集合的 Filter ,最终会用于构建 springSecurityFilterChain , 那么 springSecurityFilterChain 又是干什么的呢?
三 . Security Filter Chain
Security 的 Filter 和 WebFilter 本质是一样的 , 只是为了实现 Seccurity 的功能
>>> 来看一下 FilterChain 的调用链 :
3.1 FilterChain 的创建过程
FilterChain 的核心是一个 VirtualFilterChain , 每个请求过来都会有一个VirtualFilterChain 生成
VirtualFilterChain 是 FilterChainProxy 的内部类.
1 | java复制代码// 补充 : VirtualFilterChain : 内部过滤器链实现,用于通过与请求匹配的额外内部过滤器列表传递请求 |
VirtualFilterChain 的创建流程 :
1 | java复制代码 |
可以看到 , 这里每次执行 doFilterInternal 时都会创建一个 VirtualFilterChain .
主要抽象类 AbstractAuthenticationProcessingFilter
1 | java复制代码C- AbstractAuthenticationProcessingFilter |
我之前以为 Security 的方式是把 所有的 Filter 走一遍后再执行 Provider , 从这里看来他采用的是Filter 适配后就直接执行 Provider
3.2 WebAsyncManagerIntegrationFilter
1 | java复制代码 |
3.3 SecurityContextPersistenceFilter 体系
注意 , SecurityContext 是整个认证的核心 , 拥有 SecurityContext 即表示认证成功
1 | java复制代码C- SecurityContextPersistenceFilter |
3.4 HeaderWriterFilter 体系
List<HeaderWriter> headerWriters
: 构造对象的时候会写入一个list
核心方法 : doFilterInternal
- 准备了2个 HeaderWriterResponse , HeaderWriterRequest , 他们支持对Header 的二次封装 (原本的 Servlet 是不支持的)
- filterChain 完成后会执行 headerWriterResponse.writeHeaders();
1 | java复制代码// eaderWriterResponse.writeHeaders() 是从 HeadersConfigurer 中获取的 |
3.5 LogoutFilter
LogoutFilter 允许定制LogoutHandler , 这一点在构造函数里面就能看到
可以看到 , 默认使用 logout 地址作为拦截请求
1 | java复制代码public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, |
3.6 CsrfFilter
之前了解到 , 为了使同步器令牌模式能够防止 CSRF 攻击,必须在 HTTP 请求中包含实际的 CSRF 令牌。这必须包含在浏览器不会自动包含在 HTTP 请求中的请求的一部分(即表单参数、 HTTP 头等)中。
Spring Security 的 CsrfFilter 将一个 CsrfToken 作为一个名为 _csrf 的 HttpServletRequest 公开属性
1 | java复制代码 |
3.9 其他零散Token
如果缓存的请求与当前请求匹配,则负责重新构造已保存的请求
整个核心代码主要是 2句话:其中主要是封装了一个新得 wrappedSavedRequest
1 | java复制代码HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest( |
SecurityContextHolderAwareRequestFilter
一个过滤器,它使用实现servlet API安全方法的请求包装器填充ServletRequest简单点说 , 就是一个封装 Request 的 Filter , 封装的 HttpServletRequest 提供了很多额外的功能
- HttpServletRequest.authenticate() - 允许用户确定他们是否被验证,如果没有,则将用户发送到登录页面
- HttpServletRequest.login() - 允许用户使用AuthenticationManager进行身份验证
- HttpServletRequest.logout() - 允许用户使用Spring Security中配置的LogoutHandlers注销
SessionManagementFilter
SessionManagementFilter 中提供了多个对象用于在用户已经认证后进行 Session 会话活动 ,** 激活会话固定保护机制或检查多个并发登录**
- SecurityContextRepository securityContextRepository;
- SessionAuthenticationStrategy sessionAuthenticationStrategy;
3.8 ExceptionTranslationFilter
作用 : 处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException。
如果检测到AuthenticationException,过滤器将启动authenticationEntryPoint。这允许通用地处理来自AbstractSecurityInterceptor的任何子类的身份验证失败。
sendStartAuthentication(request, response, chain,(AuthenticationException) exception);
如果检测到AccessDeniedException,筛选器将确定该用户是否是匿名用户。
- 如果它们是匿名用户,则将启动authenticationEntryPoint。
- 如果它们不是匿名用户,则筛选器将委托给AccessDeniedHandler。
- 默认情况下,过滤器将使用AccessDeniedHandlerImpl。
1 | java复制代码sendStartAuthentication(request,response,chain,new InsufficientAuthenticationException( |
(PS : 因为是链式结构 , 所以他作为最后一个 , 也是处在最外层的)
核心是通过一个 catch 来处理
1 | java复制代码try { |
- 首先,ExceptionTranslationFilter 调用 FilterChain.doFilter (请求、响应)来调用应用程序的其余部分。
- 如果用户没有经过身份验证,或者它是 AuthenticationException,那么启动身份验证。
- 清空 Security contextholder
- 将 HttpServletRequest 保存在 RequestCache 中。当用户成功进行身份验证时,使用 RequestCache获取原始请求
- AuthenticationEntryPoint 用于从客户机请求凭据。
- 例如,它可能会重定向到一个登录页面,或者发送一个 WWW-Authenticate 标头。
- 否则,如果它是一个 AccessDeniedException,那么 Access Denied
如果应用程序没有抛出 AccessDeniedException 或 AuthenticationException,那么 ExceptionTranslationFilter 不会做任何事情。
四 . 业务流
谈到了 Filter , 肯定就要细聊 Filter 对应的业务 , 上面说了一些简单的 Filter 业务 , 这一段我们来说一说比较大的业务流程 :
4.1 HttpSecurity 的业务匹配
我们在配置 Security 的时候 , 一般都会配置 Request Match 等参数 , 例如 :
1 | java复制代码 http.authorizeRequests() |
那么这些参数是怎么生效的呢 ?
Step End : 最终匹配对象
我们来根据整个业务流程逆推 , 其最终对象是一个 RequestMatcher 实现类
注意 , 我们其上的 antMatchers 类型会生成多种不同的实现类 :
- AndRequestMatcher : 请求路径
- IpAddressMatcher : IP地址
- MediaTypeRequestMatcher : 媒体类型
…. 等等其他的就不详细说了
拿到实现类后 ,调用 实现类的matches 方法返回最终结果
1 | java复制代码public boolean matches(HttpServletRequest request) { |
Step Start : 看看请求的起点
找到了最终的匹配点 , 后面就好说了 , 打个断点 , 整个调用链就清清楚楚了
1 | java复制代码 |
这里可以看到 , 如果没有被拦截成功的 ,最终应该就直接运行了 , 所以 Security 一切的起点都是 Filter
五.补充
5.1 DelegatingFilterProxy 补充
Security 通过 DelegatingFilterProxy 将 Security 融入到 WebFilter 的体系中 ,其主要流程为 :
- C- DelegatingFilterProxy # doFilter
- C- DelegatingFilterProxy # invokeDelegate
- C- FilterChainProxy # doFilter
1 | JAVA复制代码public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) |
PIC51001 : delegate 对象结构
继续补充 : delegate 的初始化 , 获取 FilterChain
1 | java复制代码protected Filter initDelegate(WebApplicationContext wac) throws ServletException { |
总结
Spring Security 的起点就是 Filter ,常用的功能都能通过 Filter 找到相关的痕迹 , 后续我们会继续分析更底层的东西, 来开枝散叶的看看底下经历了什么
Security 的 Filter 和 WebFilter 的本质一样 , Security 通过一个 DelegatingFilterProxy 将 SecurityFilterChain 集中到 Filter 体系中
FilterChain 的核心是一个 VirtualFilterChain , 每个请求过来都会有一个VirtualFilterChain 生成 ,其中会添加所有的 Filter 类
的 Filter 包括 :
- SecurityContextPersistenceFilter : 对 SecurityContext 进行持久化操作
- HeaderWriterFilter : 对 Header 进行二次处理 , 因为很多认证信息会放在 Header 中 , 这也是一个极其重要的类
- LogoutFilter : 拦截 logout 请求 , 并且退出
- ExceptionTranslationFilter : 对流程中的异常进行处理 (AccessDeniedException和AuthenticationException)
Filter 会通过 matches 进行拦截 , 判断是否要执行 Filters 逻辑
更新日志
V20210803 : 补充 DelegatingFilterProxy 逻辑
本文转载自: 掘金