目录

问题

分析问题

服务器的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()方法调用到

DelegatingFilterProxyRegistrationBeangetFilter()方法:

    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()失效问题源码分析相关推荐

  1. Spring自定义注解驱动开发使用及源码分析

    目录 前言 注解驱动开发使用 需求 代码实现 测试效果 源码分析 BeanDefinitionRegistryPostProcessor接口 解析BeanDefinition 处理Bean上配置的注解 ...

  2. FFmpeg的抽帧filter:select的应用与源码分析

    1. select应用实践 select用于选择哪些帧进入到后续的处理流程, 或称为 抽帧 滤镜: 基于ffmeg进行抽帧共有四种方式: > 抽取视频指定类型的帧(I/P/B) > 抽取视 ...

  3. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  4. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  5. Dubbo篇:基于Netty实现Dubbo协议编解码源码分析

    Dubbo协议解析 Dubbo协议设计参考了TCP/IP协议,包括协议头和协议体两部分.16字节报文头主要携带了魔法数(0xdabb,用于分割两个不同请求),以及当前请求报文是否是Request.Re ...

  6. 源码分析Dubbo系列文章

       本系列文章主要针对Dubbo2.6.2(dubbox2.8.4)版本,从源码的角度分析Dubbo内部的实现细节,加深对Dubbo的各配置参数底层实现原理的理解,更好的指导Dubbo实践,其目录如 ...

  7. @Transactional的用法详解及Transactional事务无效的源码分析

    数据库事务正确执行的四要素 1.原子性 事务是不可分割的最小的工作单元,事务内的操作要么全做,要么全不做,不能只做一部分. 2.一致性 事务执行前数据库的数据按照逻辑处于正确的状态,事务执行后数据库的 ...

  8. asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证

    asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型的绑定和验证 原文:asp.net mvc源码分析-DefaultModelBinder 自定义的普通数据类型 ...

  9. 【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发机制...

    前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五 ...

最新文章

  1. MySQL安装ODBC驱动出现126错误
  2. FM:大熊猫的肠道菌群可能并没有特化出发酵纤维素的能力
  3. 随机梯度下降、批量梯度下降、小批量梯度下降分类是什么?有什么区别?batch_size的选择如何实施、有什么影响?
  4. 安装库_Python快速安装库的靠谱办法
  5. 怎么设置php.ini允许sql语句插入空值到mysql里_php读取.sql文件,写入mysql,navicat显示乱码,编码设置...
  6. MySQL事务及字符集介绍
  7. 技术沙龙|赋能企业数字化转型,移动云云原生应用架构实践
  8. 几何畸变图像恢复 openCV3 - 数字图像处理作业3
  9. 基于SSM的培训机构管理系统
  10. ios设置中性黑体_iOS 自定义-苹方字体的使用
  11. 《算法竞赛 入门经典》
  12. [转]如何在Web页面上直接打开、编辑、创建Office文档
  13. java和3d建模_基于Java3D技术和Swing技术的3D建模开发
  14. 三菱FX2N:PC与PLC建立通讯的几种方式(SC-09通讯电缆+FX2N-485-BD通讯板)
  15. CVE-2014-6271 “破壳“ 漏洞
  16. 绍兴市越城区人大常委会主任徐荻一行莅临迪捷软件调研指导
  17. 亚洲的音乐史料及其历史研究状况
  18. 如何快速查看bili上的视频学习
  19. 前端智能化实践(附:D2 前端技术论坛 PPT 合集)
  20. JAVASE温故知新

热门文章

  1. Android学习羁绊之UI设计
  2. eset linux,ESET Endpoint Antivirus for Linux下载,支持Ubuntu、RedHat系统
  3. 60行python代码打造打字训练器小游戏(PyQt5)
  4. 中国同步带轮市场趋势报告、技术动态创新及市场预测
  5. 阿里飞猪电话面一个小时9分钟
  6. join()方法的神奇用处与Intern机制的软肋
  7. 还不会恋爱吗?看看我和MySQL的高质量恋爱……
  8. 必备mysql技能(资料来自韩顺平哔哩哔哩)
  9. 2023秋招面试经验(华为、海康、中兴等)
  10. web day25 web day24 小项目练习图书商城, 购物车模块,订单模块,支付(易宝支付)