SpringSecurity自定义Filter的ignoring()失效问题源码分析

本文深入探讨了Spring Boot应用中过滤器的注册与工作原理,重点分析了Spring Security的Filter实现,包括过滤链的创建、过滤器的忽略配置以及SecurityFilter的代理类生成。在实践中遇到的问题是自定义的JwtAuthenticationFilter未被ignoring()配置忽略,原因是自定义的Filter被注册为Spring Bean导致不受SecurityConfig控制。解决方案是通过理解Spring Security的过滤器链和代理机制,确保自定义过滤器正确地由Security管理。

目录

问题

分析问题

服务器的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)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .anyRequest()
                .permitAll();

        http.addFilter(jwtAuthenticationFilter());
    }


    @Override
    public 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的关系:

宠辱不惊 看庭前花开花落

去留无意 看天上云卷云舒

                                                                                        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

知始行末

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值