SpringSecurity自定义Filter的ignoring()失效问题源码分析
目录
问题
分析问题
服务器的Filter实现原理
注册Filter
Filter过滤流程
Security Filter原理
代理类生成
配置的注入
Filter注册为bean的问题
总结
问题
如下面代码,将JwtAuthenticationFilter注入到spring中,然后通过HttpSecurity对象将Filter添加到SecrityConfig配置中,然后配置的ignoring()不会生效。每次请求还是会走JwtAuthenticationFilter这个Filter。
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Bean(BeanIds.AUTHENTICATION_MANAGER)@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Beanpublic JwtAuthenticationFilter jwtAuthenticationFilter() {return new JwtAuthenticationFilter();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().anyRequest().permitAll();http.addFilter(jwtAuthenticationFilter());}@Overridepublic void configure(WebSecurity web) {web.ignoring().antMatchers("/auth","/");}
}
查阅了很多资料都没看到有人在源码层面上分析,于是自己根据各个模块源码梳理了下流程。
分析问题
想要解决这个问题,必须了解Secutity的Filter和Spring Filter实现的区别。
阅读源码,让我们一步一步揭开他们的神秘面纱。
服务器的Filter实现原理
注册Filter
这是调用栈,通过debug的方式,让我们分析源码更加简单:
Spring Boot 启动入口
public static void main(String[] args) {SpringApplication.run(Application.class, args);}
SpringApplication中run的主要流程:
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);this.configureIgnoreBeanInfo(environment);Banner printedBanner = this.printBanner(environment);//获取需要刷新的上下文
context = this.createApplicationContext();exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);this.prepareContext(context, environment, listeners, applicationArguments, printedBanner);//刷新上下文this.refreshContext(context);this.afterRefresh(context, applicationArguments);stopWatch.stop();
获取到 AnnotationConfigServletWebServerApplicationContext上下文:
protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch(this.webApplicationType) {case SERVLET:contextClass = Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");break;case REACTIVE:contextClass = Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");break;default:contextClass = Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");}} catch (ClassNotFoundException var3) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", var3);}}
然后到ServletWebServerApplicationContext中调用createWebServer() 方法创建服务器:
protected void onRefresh() {super.onRefresh();try {this.createWebServer();} catch (Throwable var2) {throw new ApplicationContextException("Unable to start web server", var2);}}private void createWebServer() {WebServer webServer = this.webServer;ServletContext servletContext = this.getServletContext();if (webServer == null && servletContext == null) {ServletWebServerFactory factory = this.getWebServerFactory();this.webServer = factory.getWebServer(new ServletContextInitializer[]{this.getSelfInitializer()});} else if (servletContext != null) {try {this.getSelfInitializer().onStartup(servletContext);} catch (ServletException var4) {throw new ApplicationContextException("Cannot initialize servlet context", var4);}}this.initPropertySources();}
在this.getSelfInitializer()中初始化配置:
private ServletContextInitializer getSelfInitializer() {return this::selfInitialize;}private void selfInitialize(ServletContext servletContext) throws ServletException {this.prepareWebApplicationContext(servletContext);this.registerApplicationScope(servletContext);WebApplicationContextUtils.registerEnvironmentBeans(this.getBeanFactory(), servletContext);// 将spring bean配置到servlet context中Iterator var2 = this.getServletContextInitializerBeans().iterator();while(var2.hasNext()) {ServletContextInitializer beans = (ServletContextInitializer)var2.next();beans.onStartup(servletContext);}}
ServletContextInitializerBeans在构造函数中将Spring bean中Filter注册到Serlet的上下文中:
public ServletContextInitializerBeans(ListableBeanFactory beanFactory, Class<? extends ServletContextInitializer>... initializerTypes) {this.initializerTypes = initializerTypes.length != 0 ? Arrays.asList(initializerTypes) : Collections.singletonList(ServletContextInitializer.class);this.addServletContextInitializerBeans(beanFactory);this.addAdaptableBeans(beanFactory);List<ServletContextInitializer> sortedInitializers = (List)this.initializers.values().stream().flatMap((value) -> {return value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE);}).collect(Collectors.toList());this.sortedList = Collections.unmodifiableList(sortedInitializers);this.logMappings(this.initializers);}private void addServletContextInitializerBeans(ListableBeanFactory beanFactory) {Iterator var2 = this.initializerTypes.iterator();while(var2.hasNext()) {Class<? extends ServletContextInitializer> initializerType = (Class)var2.next();Iterator var4 = this.getOrderedBeansOfType(beanFactory, initializerType).iterator();while(var4.hasNext()) {Entry<String, ? extends ServletContextInitializer> initializerBean = (Entry)var4.next();this.addServletContextInitializerBean((String)initializerBean.getKey(), (ServletContextInitializer)initializerBean.getValue(), beanFactory);}}}private void addServletContextInitializerBean(String beanName, ServletContextInitializer initializer, ListableBeanFactory beanFactory) {if (initializer instanceof ServletRegistrationBean) {Servlet source = ((ServletRegistrationBean)initializer).getServlet();this.addServletContextInitializerBean(Servlet.class, beanName, initializer, beanFactory, source);//FilterRegistration用于将filter注册到Serlet的上下文中(Security使用的该方法)} else if (initializer instanceof FilterRegistrationBean) {Filter source = ((FilterRegistrationBean)initializer).getFilter();this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);} else if (initializer instanceof DelegatingFilterProxyRegistrationBean) {String source = ((DelegatingFilterProxyRegistrationBean)initializer).getTargetBeanName();this.addServletContextInitializerBean(Filter.class, beanName, initializer, beanFactory, source);} else if (initializer instanceof ServletListenerRegistrationBean) {EventListener source = ((ServletListenerRegistrationBean)initializer).getListener();this.addServletContextInitializerBean(EventListener.class, beanName, initializer, beanFactory, source);} else {this.addServletContextInitializerBean(ServletContextInitializer.class, beanName, initializer, beanFactory, initializer);}}//将Servlet、Filter注册到Servlet容器中protected void addAdaptableBeans(ListableBeanFactory beanFactory) {MultipartConfigElement multipartConfig = this.getMultipartConfig(beanFactory);this.addAsRegistrationBean(beanFactory, Servlet.class, new ServletContextInitializerBeans.ServletRegistrationBeanAdapter(multipartConfig));this.addAsRegistrationBean(beanFactory, Filter.class, new ServletContextInitializerBeans.FilterRegistrationBeanAdapter());Iterator var3 = ServletListenerRegistrationBean.getSupportedTypes().iterator();while(var3.hasNext()) {Class<?> listenerType = (Class)var3.next();this.addAsRegistrationBean(beanFactory, EventListener.class, listenerType, new ServletContextInitializerBeans.ServletListenerRegistrationBeanAdapter());}}
Filter过滤流程
filter是对servlet进行过滤的,到底它是怎么起到过滤的呢?
通过debug查看调用栈的关系,可以看到Tomcat在接受到request的请求后,会先获取过滤器链:
而后遍历并调用过滤器链,发起过滤流程:
filterChain.doFilter(request.getRequest(), response.getResponse());
Security Filter原理
代理类生成
这里的我们可以看到关于spring security相关的类DelegatingFilterProxyRegistrationBean,这个
DelegatingFilterProxyRegistrationBean就是我们分析security的入口:
Spring Boot 会自动装配SecurityFilterAutoConfiguration这个类,创建DelegatingFilterProxyRegistrationBean bean,代理的目标对象是名为"springSecurityFilterChain"的bean,后续这个"springSecurityFilterChain"会在自动装配的配置文件中创建,后续的过滤入口就在它。
@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;@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.collectingAndThen(Collectors.toSet(), EnumSet::copyOf));}}
Tomcat在启动的时候会调用所有RegistrationBean的onStartup()方法:
public abstract class RegistrationBean implements ServletContextInitializer, Ordered {public final void onStartup(ServletContext servletContext) throws ServletException {String description = this.getDescription();if (!this.isEnabled()) {logger.info(StringUtils.capitalize(description) + " was not registered (disabled)");} else {this.register(description, servletContext);}}}
然后通过父类的AbstractFilterRegistrationBean的getDescription()方法调用到
DelegatingFilterProxyRegistrationBean的getFilter()方法:
protected String getDescription() {Filter filter = this.getFilter();Assert.notNull(filter, "Filter must not be null");return "filter " + this.getOrDeduceName(filter);}
为Secrity生成一个代理类,后续的过滤在代理类中完成:
public DelegatingFilterProxy getFilter() {return new DelegatingFilterProxy(this.targetBeanName, this.getWebApplicationContext()) {protected void initFilterBean() throws ServletException {}};}
配置的注入
Springboot 自动装配了SecurityAutoConfiguration配置类:
然后通过import引入WebSecurityEnablerConfiguration配置,WebSecurityEnablerConfiguration类又开启了@EnableWebSecurity注解,EnableWebSecurity类注解引入了WebSecurityConfiguration配置类。
@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {@Bean@ConditionalOnMissingBean(AuthenticationEventPublisher.class)public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {return new DefaultAuthenticationEventPublisher(publisher);}}@Configuration
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {}@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,SpringWebMvcImportSelector.class,OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {/*** Controls debugging support for Spring Security. Default is false.* @return if true, enables debug support with Spring Security*/boolean debug() default false;
}
WebSecurityConfiguration中申明了"springSecurityFilterChain" bean,
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = webSecurityConfigurers != null&& !webSecurityConfigurers.isEmpty();if (!hasConfigurers) {WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});webSecurity.apply(adapter);}return webSecurity.build();}
然后调用build()->doBuild()->init()、初始化HttpSecurity配置、初始化WebSecurity配置、初始化过滤器链、忽视的过滤器链。
//省略中间调用
protected final O doBuild() throws Exception {synchronized (configurers) {buildState = BuildState.INITIALIZING;beforeInit();init();buildState = BuildState.CONFIGURING;beforeConfigure();configure();buildState = BuildState.BUILDING;O result = performBuild();buildState = BuildState.BUILT;return result;}}
debug时可以看到生成了五个过滤器的chain,包含四个ignoring()的chain。
performBuild()返回的是一个FilterChainProxy,其实就是一个Filter,下面是类图:
后续的过滤都被FilterChainProxy代理了, 通过遍历调用chain.doFilter(fwRequest, fwResponse)完成请求的过滤:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;if (clearContext) {try {request.setAttribute(FILTER_APPLIED, Boolean.TRUE);this.doFilterInternal(request, response, chain);} finally {SecurityContextHolder.clearContext();request.removeAttribute(FILTER_APPLIED);}} else {this.doFilterInternal(request, response, chain);}}private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);if (filters != null && filters.size() != 0) {FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);vfc.doFilter(fwRequest, fwResponse);} else {if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));}fwRequest.reset();chain.doFilter(fwRequest, fwResponse);}}
doFilterInternal()中获取匹配的所有过滤器:
List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {FirewalledRequest fwRequest = this.firewall.getFirewalledRequest((HttpServletRequest)request);HttpServletResponse fwResponse = this.firewall.getFirewalledResponse((HttpServletResponse)response);List<Filter> filters = this.getFilters((HttpServletRequest)fwRequest);if (filters != null && filters.size() != 0) {FilterChainProxy.VirtualFilterChain vfc = new FilterChainProxy.VirtualFilterChain(fwRequest, chain, filters);vfc.doFilter(fwRequest, fwResponse);} else {if (logger.isDebugEnabled()) {logger.debug(UrlUtils.buildRequestUrl(fwRequest) + (filters == null ? " has no matching filters" : " has an empty filter list"));}fwRequest.reset();chain.doFilter(fwRequest, fwResponse);}}
在之前的四个忽略的过滤器链中会匹配到,但是Filters是空的。因为在初始化的时候已经根据配置把Filters设置为空了:
private List<Filter> getFilters(HttpServletRequest request) {Iterator var2 = this.filterChains.iterator();SecurityFilterChain chain;do {if (!var2.hasNext()) {return null;}chain = (SecurityFilterChain)var2.next();} while(!chain.matches(request));return chain.getFilters();}
可以看到我们忽略的请求的过滤器是空的,这时候直接跳过:
Filter注册为bean的问题
如果将我们在security中的filter注册为bean,那么它就是一个全局的过滤器,如上面关于tomcat 是如何加载Filter的分析。
如下图是Tomcat获取的所有过滤器链。FilterBean会被加载到servler上下文,和security 的FilterChainProxy同一等级。
将不会受security config的控制。配置的ignoring()将会失效,每次请求都会走到你自定义的filter中。
所以我们必须new一个对象,把对象交给Security管理,才能时ignoring()生效。
总结
其中涉及到源码模块分别为:
- Spring Boot中服务器的启动流程,这里初始化我们Filter;
- Spring Boot中服务器接受请求,过滤器链调用过程;
- Spring Security自动装配Filter代理类、ignore()等配置信息装配过程;
- Spring Security真实Filters调用过程;
根据上述几点结合源码,通过端点、线程栈的查看基本能清楚其脉络。
下图简要概括了Secutiry的Filter和Spring Filter的Bean的关系:
宠辱不惊 看庭前花开花落
去留无意 看天上云卷云舒
SpringSecurity自定义Filter的ignoring()失效问题源码分析相关推荐
- Spring自定义注解驱动开发使用及源码分析
目录 前言 注解驱动开发使用 需求 代码实现 测试效果 源码分析 BeanDefinitionRegistryPostProcessor接口 解析BeanDefinition 处理Bean上配置的注解 ...
- FFmpeg的抽帧filter:select的应用与源码分析
1. select应用实践 select用于选择哪些帧进入到后续的处理流程, 或称为 抽帧 滤镜: 基于ffmeg进行抽帧共有四种方式: > 抽取视频指定类型的帧(I/P/B) > 抽取视 ...
- jQuery源码分析系列
声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...
- [转]jQuery源码分析系列
文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...
- Dubbo篇:基于Netty实现Dubbo协议编解码源码分析
Dubbo协议解析 Dubbo协议设计参考了TCP/IP协议,包括协议头和协议体两部分.16字节报文头主要携带了魔法数(0xdabb,用于分割两个不同请求),以及当前请求报文是否是Request.Re ...
- 源码分析Dubbo系列文章
本系列文章主要针对Dubbo2.6.2(dubbox2.8.4)版本,从源码的角度分析Dubbo内部的实现细节,加深对Dubbo的各配置参数底层实现原理的理解,更好的指导Dubbo实践,其目录如 ...
- @Transactional的用法详解及Transactional事务无效的源码分析
数据库事务正确执行的四要素 1.原子性 事务是不可分割的最小的工作单元,事务内的操作要么全做,要么全不做,不能只做一部分. 2.一致性 事务执行前数据库的数据按照逻辑处于正确的状态,事务执行后数据库的 ...
- asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证
asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型 ...
- 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...
前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...
最新文章
- MySQL安装ODBC驱动出现126错误
- FM:大熊猫的肠道菌群可能并没有特化出发酵纤维素的能力
- 随机梯度下降、批量梯度下降、小批量梯度下降分类是什么?有什么区别?batch_size的选择如何实施、有什么影响?
- 安装库_Python快速安装库的靠谱办法
- 怎么设置php.ini允许sql语句插入空值到mysql里_php读取.sql文件,写入mysql,navicat显示乱码,编码设置...
- MySQL事务及字符集介绍
- 技术沙龙|赋能企业数字化转型,移动云云原生应用架构实践
- 几何畸变图像恢复 openCV3 - 数字图像处理作业3
- 基于SSM的培训机构管理系统
- ios设置中性黑体_iOS 自定义-苹方字体的使用
- 《算法竞赛 入门经典》
- [转]如何在Web页面上直接打开、编辑、创建Office文档
- java和3d建模_基于Java3D技术和Swing技术的3D建模开发
- 三菱FX2N:PC与PLC建立通讯的几种方式(SC-09通讯电缆+FX2N-485-BD通讯板)
- CVE-2014-6271 “破壳“ 漏洞
- 绍兴市越城区人大常委会主任徐荻一行莅临迪捷软件调研指导
- 亚洲的音乐史料及其历史研究状况
- 如何快速查看bili上的视频学习
- 前端智能化实践(附:D2 前端技术论坛 PPT 合集)
- JAVASE温故知新
热门文章
- Android学习羁绊之UI设计
- eset linux,ESET Endpoint Antivirus for Linux下载,支持Ubuntu、RedHat系统
- 60行python代码打造打字训练器小游戏(PyQt5)
- 中国同步带轮市场趋势报告、技术动态创新及市场预测
- 阿里飞猪电话面一个小时9分钟
- join()方法的神奇用处与Intern机制的软肋
- 还不会恋爱吗?看看我和MySQL的高质量恋爱……
- 必备mysql技能(资料来自韩顺平哔哩哔哩)
- 2023秋招面试经验(华为、海康、中兴等)
- web day25 web day24 小项目练习图书商城, 购物车模块,订单模块,支付(易宝支付)