〇、上篇回顾
- 上篇文章学习了如何通过建造者和配置器创建出核心过滤器
springSecurityFilterChain
,但也只是个加入Bean容器的对象而已,对于如何在项目中运行起来的,要让我们创建的 Filter 有效需要将其加入 Servlet 容器,一起来看看。
一、关于 SpringBoot 中 Filter 加入 Servlet 容器的方式
在学习如何将核心过滤器加入 Servlet 容器之前,我们先来了解 SpringBoot 中添加 Filter 到 Servlet 容器的方式。
方式一
- 使用
@WebFilter
注解,@ServletComponentScan
所扫描的包路径必须包含该 Filter
@WebFilter(filterName = "myFilter",urlPatterns = "/*")
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
}
@SpringBootApplication
@EnableAutoConfiguration
@EnableWebMvc
@ServletComponentScan(basePackages = "com.fanyin.eghm")
public class EghmApplication {
public static void main(String[] args) {
SpringApplication.run(EghmApplication.class, args);
}
}
方式二
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean filterRegistrationBean(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new MyFilter2());
bean.addUrlPatterns("/*");
return bean;
}
}
方式三
@Bean("proxyFilter")
public Filter filter (){
return new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
};
}
@Bean
public DelegatingFilterProxyRegistrationBean delegatingFilterProxyRegistrationBean(){
DelegatingFilterProxyRegistrationBean bean = new DelegatingFilterProxyRegistrationBean("proxyFilter");
bean.addUrlPatterns("/*");
return bean;
}
-
第二种和第三种类似,均实现了
AbstractFilterRegistrationBean
接口,而该接口间接实现了ServletContextInitializer
,SpringBoot 在启动容器后会查找实现该接口的 Bean,并调用 onStartup() 方法添加自定义的 Filter,两者的区别:DelegatingFilterProxyRegistrationBean
通过传入的 proxyFilter 名字,在 WebApplicationContext 查找该 Fillter Bean,并通过 DelegatingFilterProxy 生成基于该 Bean 的代理 Filter 对象。FilterRegistrationBean
则是直接设置一个Filter,因此该 Filter 可以有 Spring 容器管理,也可不用 Spring 管理。
方式四
- 如果
Filter 声明为一个 Bean
,则不需要定义为 FilterRegistrationBean,也会被 Spring 发现并添加,就是方法四,该方式无法定义拦截规则等,默认全局,慎用。
@Bean("myFilter")
public Filter filter() {
return new Filter() {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
}
@Override
public void destroy() {
}
};
}
本小节参考来源:https://www.jianshu.com/p/3d421fbce734
二、核心过滤器是如何加入 Sevlet 容器的?
SecurityFilterAutoConfiguration
其实核心过滤器的加入方式使用的是上面提到的方式三,SpringBoot 提供了这么一个类:
SecurityFilterAutoConfiguration
@Configuration(proxyBeanMethods = false)
// 仅在 Servlet 环境下生效
@ConditionalOnWebApplication(type = Type.SERVLET)
// 确保安全属性配置信息被加载并以 bean 形式被注册到容器
@EnableConfigurationProperties(SecurityProperties.class)
// 仅在特定类存在于 classpath 上时才生效
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
// 指定该配置类在 SecurityAutoConfiguration 配置类应用之后应用
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
// 要注册到 Servlet 容器的 DelegatingFilterProxy Filter的目标代理 Filter Bean 的名称:springSecurityFilterChain,
// 会发现实际上就是我们的核心过滤器的 Bean。
private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
// 定义一个 securityFilterChainRegistration 的 Bean,其目的是注册另外一个 Bean 到 Servlet 容器
// 实现类为 DelegatingFilterProxy 的一个 Servlet Filter,该 DelegatingFilterProxy Filter 其实是一个代理过滤器
// 它被 Servlet 容器用于匹配特定URL模式的请求,而它会将任务委托给名字为 springSecurityFilterChain 的 Filter
@Bean
@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
SecurityProperties securityProperties) {
DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
DEFAULT_FILTER_NAME);
registration.setOrder(securityProperties.getFilter().getOrder());
registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
return registration;
}
private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
if (securityProperties.getFilter().getDispatcherTypes() == null) {
return null;
}
return securityProperties.getFilter().getDispatcherTypes().stream()
.map((type) -> DispatcherType.valueOf(type.name()))
.collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
}
}
类说明
- 该类主要定义了一个 securityFilterChainRegistration 的 Bean,其目的是
注册另外一个 Bean 到 Servlet 容器
,实现类为 DelegatingFilterProxy 的一个 Servlet Filter。 - 这个
DelegatingFilterProxy Filter
其实是一个代理过滤器,它被 Servlet 容器用于匹配特定 URL 模式的请求,而它会将任务委托给名字为 springSecurityFilterChain 的 Filter。 - 正好,我们 Spring Security 的核心过滤器名字就是
springSecurityFilterChain
,其实现类是 FilterChainProxy。
三、运行过程
FilterChainProxy
过滤器已经加入了ServletContext
,请求到达的时候会调用doFilter()
doFilter()
里面进入方法doFilterInternal()
,里面getFilters()
方法返回第一条匹配中请求的过滤器链,然后使用过滤器链(filters)和请求(fwRequest)以及原始过滤器链(chain)共同创建出类型为VirtualFilterChain
虚拟过滤器链对象vfc
,目的是为了在过滤器链上传递请求,这里便是典型的责任链模式。- 之后请求开始被所有
Filter
处理,所有 Filter 都处理过之后会使用原始过滤器调用doFilter(request, response)
继续被其他的过滤器处理,如下截取了部分代码:
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException {
if (currentPosition == size) {
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " reached end of additional filter chain; proceeding with original chain");
}
// Deactivate path stripping as we exit the security filter chain
this.firewalledRequest.reset();
originalChain.doFilter(request, response);
}
else {
currentPosition++;
Filter nextFilter = additionalFilters.get(currentPosition - 1);
if (logger.isDebugEnabled()) {
logger.debug(UrlUtils.buildRequestUrl(firewalledRequest)
+ " at position " + currentPosition + " of " + size
+ " in additional filter chain; firing Filter: '"
+ nextFilter.getClass().getSimpleName() + "'");
}
nextFilter.doFilter(request, response, this);
}
}
四、总结
- 首先我们需要将过滤器加入到
ServletContext
才能起作用 - 要了解 SpringBoot 中
如何将 Filter 加入 Servlet 容器
- 最后要知道
Spring Security
的过滤器链也只是整个请求处理的一环
五、系列文章
Spring Security 系列
- 《手把手教你如何使用Spring Security(上):登录授权》
- 《手把手教你如何使用Spring Security(中):接口认证》
- 《手把手教你如何使用Spring Security(下):访问控制》
- 《Spring Security源码(一):整体框架设计》
- 《Spring Security源码(二):建造者详解》
- 《Spring Security源码(三):HttpSecurity详解》
- 《Spring Security源码(四):配置器详解》
- 《Spring Security源码(五):FilterChainProxy是如何创建的?》
- 《Spring Security源码(六):FilterChainProxy是如何运行的?》
- 《Spring Security源码(七):设计模式在框架中的应用》
- 《Spring Security源码(八):登录认证源码流程》
- 《Spring Security源码(九):过滤器链上的过滤器是如何排序的?》
- 《Spring Security源码(十):权限访问控制是如何做到的?》
Spring Security OAuth 系列
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。