SpringSecurity源码分析(一) SpringBoot集成SpringSecurity即Spring安全框架的加载过程

2023-10-30

      Spring Security是一个强大的并且高度可定制化的访问控制框架。 它基于spring应用。

Spring Security是聚焦于为java应用提供授权和验证的框架。像所有的spring项目一样,Spring Security真正的强大在于可以非常简单的拓展功能来实现自定义的需求。

      在分析SpringBoot集成的SpringSecurity源码时,一般可以分为两部分来分析Spring安全框架的源码。

      一、SpringSecurity在SpringBoot框架的启动过程中的加载过程。

      二、SpringSecurity在请求执行过程当中的执行过程。

     现在我根据上面的两个过程对SpringSecurity的源码进行分析。

     在分析时我们需要在springboot项目中引入SpringSecurity的maven依赖配置。该配置如下所示:

        <!--spring 安全框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

注意:我分析的springsecurity版本是5.0.7-RELEASE 

 SpringSecurity在SpringBoot框架的启动过程中的加载过程。

     SpringSecurity框架在调用过程中通过配置的方式往spring容器中注入很多bean,为调用过程中做准备。

     在SpringSecurity中存在很多配置类,负责在springboot启动时往容器注入bean。本文主要分析一下设计注入的类。

     在SpringSecurity框架中有三个非常核心的类和接口,分别是

            1.SecurityFilterChain接口

            2.FilterChainProxy类

            3.DelegatingFilterProxy类

这个三个接口和类的相互之间的步骤关系如下:

    第一步:生成一个FilterChainProxy类型的对象,其中的属性filterChains是SecurityFilterChain类型的List集合,该对象是一个被spring容器管理名称为springSecurityFilterChain类型为FilterChainProxy的bean。

    第二步:生成一个DelegatingFilterProxy类型的对象,将beanName即springSecurityFilterChain作为DelegatingFilterProxy类型对象属性targetBeanName的值,供后面请求时获取bean。这样FilterChainProxy类型的对象就被DelegatingFilterProxy类型的对象委托管理了。

      DelegatingFilterProxy对象的生成是tomcat启动过程中会调用所有继承了RegistrationBean类的

onStartUp方法,最终调用了实现类中的addRegistration方法。RegistrationBean类中onStartUp方法的调用逻辑可以参考我写的springMvc分析第一章

SpringMvc源码分析(一):启动tomcat服务器,加载DispatcherServlet并将DispatcherServlet纳入tomcat管理_xl649138628的博客-CSDN博客

其调用链路是

    +RegistrationBean#onStartup

        +DynamicRegistrationBean#register

           +AbstractFilterRegistrationBean#addRegistration(往StandardContext设置拦截器类型为DelegatingFilterProxy)

              +DelegatingFilterProxyRegistrationBean#getFilter()

第三步:前端发起请求时,调用了DelegatingFilterProxy类型的拦截器执行doFilter方法。doFilter方法获取被委托的对象FilterChainProxy并调用其doFilter方法。FilterChainProxy的doFilter方法,执行获取到的所有的拦截器然后再获取代理对象执行容器加载时保存的拦截器再执行。

本文主要分析第一步和第二步。

1.生成一个FilterChainProxy类型的对象

   1.1分析配置文件并生成beanName为springSecurityFilterChain的Bean

    1.1.1 SpringSecurity拦截器示意图

    在SpringSecurity官方文档中可以看到如下示意图。

    该图中清楚的可以看到请求访问时,DelegatingFilterProxy管理FilterChainProxy,FilterChainProxy里调用SecurityFilterChain类型的过滤器。

在这里插入图片描述

下面我会详细的分析这三个类和对象是怎么关联起来 。

 1.1.2 安全框架的配置类及如何加载

    在springboot启动过程中会获取spring.factories配置文件里的配置类并加载到spring容器中,观察spring.factories配置文件里的配置内容,涉及到springsecurity的如下图红框处所示。

      我们首先关注SecurityAutoConfiguration和SecurityFilterAutoConfiguration这两个配置类。

深入分析这些类,我们可以看到配置类的关系如下图所示:

1.1.3配置类是如何获取到 springSecurityFilterChain这个bean的

 首先分析SecurityAutoConfiguration配置类

@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
//导入属性配置文件,内部声明了user类
@EnableConfigurationProperties(SecurityProperties.class)
//导入并加载下面三个配置类到Spring容器管理
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {
    //发布认证事件,当AuthenticationEventPublisher bean不存在时加载
	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(
			ApplicationEventPublisher publisher) {
        //内部实现就是spring的ApplicationEventPublisher,
        // 用于springsecurity各种权限时间的交互,如登陆失败,会发布一个事件,
        // 然后通知其它组件做出相应的响应
		return new DefaultAuthenticationEventPublisher(publisher);
	}

}

分析该配置类里导入的三个配置类SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,SecurityDataConfiguration.class

1.SpringBootWebSecurityConfiguration配置类

作用是WebSecurityConfigurerAdapter 类存在但是bean对象不存在时注册默认的WebSecurityConfigurerAdapter 类型是DefaultConfigurerAdapter的bean。

@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
    //往容器中注入WebSecurityConfigurerAdapter类型的Bean,后面的逻辑会使用
	@Configuration
	@Order(SecurityProperties.BASIC_AUTH_ORDER)
	static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

	}

}

2.WebSecurityEnablerConfiguration配置类

该类文件如下,可以看到其中声明了@EnableWebSecurity注解

//WebSecurityConfigurerAdapter bean对象存在
@ConditionalOnBean(WebSecurityConfigurerAdapter.class)
//没有名称为springSecurityFilterChain的bean
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {

}

进入@EnableWebSecurity注解

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
		SpringWebMvcImportSelector.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和SpringWebMvcImportSelector两个配置类,声明了@EnableGlobalAuthentication注解。先研究WebSecurityConfiguration类

2.1WebSecurityConfiguration类

    类中的英文注释如下:

 * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web
 * based security for Spring Security. It then exports the necessary beans. Customizations
 * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter}
 * and exposing it as a {@link Configuration} or implementing
 * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This
 * configuration is imported when using {@link EnableWebSecurity}.

意思是:基于SpringSecurity框架的安全性执行web使用WebSecurity去创建一个FilterChainProxy。然后输出需要的bean.通过继承WebSecurityConfigurerAdapter并且声明为一个Configuration配置或者继承WebSecurityConfigurer并且声明为一个Configuration配置来实现定制化。这个WebSecurityConfiguration类是通过EnableWebSecurity注解引入的。

分析WebSecurityConfiguration类重点关注以下代码

	@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
	public Filter springSecurityFilterChain() throws Exception {
		boolean hasConfigurers = webSecurityConfigurers != null
				&& !webSecurityConfigurers.isEmpty();
		if (!hasConfigurers) { //注意hasConfigurers为true,此部分逻辑不执行
			WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
					.postProcess(new WebSecurityConfigurerAdapter() {
					});
            //往AbstractConfiguredSecurityBuilder类型对象的configurers属性
            //(该属性是LinkedHashMap类型)中添加了一个WebSecurityConfigurerAdapter类型的数据
			webSecurity.apply(adapter);
		}
		return webSecurity.build();
	}

该端代码往spring容器中注入了一个名称为springSecurityFilterChain的bean。

1.1.4分析springSecurityFilterChain,并分析springSecurityFilterChain是如何获取到过滤器并管理过滤器的

再源码中可以看到这段代码

WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
      .postProcess(new WebSecurityConfigurerAdapter() {
      });

该代码是通过 objectObjectPostProcessor调用的,objectObjectPostProcessor对象是通过

	@Autowired(required = false)
	private ObjectPostProcessor<Object> objectObjectPostProcessor;

注入到WebSecurityConfiguration配置类对象里的。objectObjectPostProcessor这个bean是通过@EnableWebSecurity注解里的@EnableGlobalAuthentication里的引入的AuthenticationConfiguration.class里引入的ObjectPostProcessorConfiguration.class注入的。注入代码如下:

@Configuration
public class ObjectPostProcessorConfiguration {

	@Bean
	public ObjectPostProcessor<Object> objectPostProcessor(
			AutowireCapableBeanFactory beanFactory) {
		return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
	}
}

继续回到objectObjectPostProcessor .postProcess(new WebSecurityConfigurerAdapter() { });方法。因为objectObjectPostProcessor是AutowireBeanFactoryObjectPostProcessor类型的,所以调用的是AutowireBeanFactoryObjectPostProcessor的postProcess方法。

在postProcess方法的源码中,可以看到其将传入的对象生成一个bean并注入到spring容器中。

由于这个对象是new WebSecurityConfigurerAdapter()生成的,所以生成的是一个WebSecurityConfigurerAdapter类型的bean。

public <T> T postProcess(T object) {
		if (object == null) {
			return null;
		}
		T result = null;
		try {
			result = (T) this.autowireBeanFactory.initializeBean(object,
					object.toString());
		}
		catch (RuntimeException e) {
			Class<?> type = object.getClass();
			throw new RuntimeException(
					"Could not postProcess " + object + " of type " + type, e);
		}
		this.autowireBeanFactory.autowireBean(object);
		if (result instanceof DisposableBean) {
			this.disposableBeans.add((DisposableBean) result);
		}
		if (result instanceof SmartInitializingSingleton) {
			this.smartSingletons.add((SmartInitializingSingleton) result);
		}
		return result;
	}

继续分析springSecurityFilterChain方法里的webSecurity.apply(adapter)方法。我们发现webSecurity对象的赋值是在以下源码中实现的

	@Autowired(required = false)
	public void setFilterChainProxySecurityConfigurer(
			ObjectPostProcessor<Object> objectPostProcessor,
			@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
			throws Exception {
		webSecurity = objectPostProcessor
				.postProcess(new WebSecurity(objectPostProcessor));
		if (debugEnabled != null) {
			webSecurity.debug(debugEnabled);
		}
        //根据继承WebSecurityConfigurer配置类的@Order注解进行排序
		Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE);

		Integer previousOrder = null;
		Object previousConfig = null;
		for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
			Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
            //如果有同等级的配置类抛出异常
			if (previousOrder != null && previousOrder.equals(order)) {
				throw new IllegalStateException(
						"@Order on WebSecurityConfigurers must be unique. Order of "
								+ order + " was already used on " + previousConfig + ", so it cannot be used on "
								+ config + " too.");
			}
			previousOrder = order;
			previousConfig = config;
		}
		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			webSecurity.apply(webSecurityConfigurer);
		}
        //将自定义继承了WebSecurityConfigurer的配置类集合赋值给webSecurityConfigurers属性
		this.webSecurityConfigurers = webSecurityConfigurers;
	}

在上面的代码中我们发现入参

@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers

这段代码的意思是调用autowiredWebSecurityConfigurersIgnoreParents对象里的getWebSecurityConfigurers方法。autowiredWebSecurityConfigurersIgnoreParents对象是通过WebSecurityConfiguration类里的以下方法注入的。

	@Bean
	public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
			ConfigurableListableBeanFactory beanFactory) {
		return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
	}

观察AutowiredWebSecurityConfigurersIgnoreParents类里的getWebSecurityConfigurers方法。

该方法的作用是获取WebSecurityConfigurer类型的bean。并返回一个list集合。该bean对象是开发者自定义的各种各样继承自WebSecurityConfigurerAdapter的配置类。如果开发者没有自定义任何配置类,那么这里获取到的就是前面所讲的SpringBootWebSecurityConfiguration 类中提供的默认配置类,将获取到的所有配置类实例放入webSecurityConfigurers集合中并返回。

	public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
		List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>();
		Map<String, WebSecurityConfigurer> beansOfType = beanFactory
				.getBeansOfType(WebSecurityConfigurer.class);
        //循环将所有的过滤器放到webSecurityConfigurers属性中。
		for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
			webSecurityConfigurers.add(entry.getValue());
		}
		return webSecurityConfigurers;
	}

另外一个入参ObjectPostProcessor<Object> objectPostProcessor 该对象是Spring注入的AutowireBeanFactoryObjectPostProcessor的bean.该bean的生成在上文有分析过。

webSecurity = objectPostProcessor
      .postProcess(new WebSecurity(objectPostProcessor));

该段代码生成了一个WebSecurity 类型的bean并对WebSecurityConfiguration类的webSecurity属性赋值。

		for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
			webSecurity.apply(webSecurityConfigurer);
		}

在上面的代码中循环获取到的自定义配置类。看见如下代码

	public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
		add(configurer);
		return configurer;
	}

重点关注上面add方法,源码如下:

	private <C extends SecurityConfigurer<O, B>> void add(C configurer) throws Exception {
		Assert.notNull(configurer, "configurer cannot be null");

		Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
				.getClass();
		synchronized (configurers) {
			if (buildState.isConfigured()) {
				throw new IllegalStateException("Cannot apply " + configurer
						+ " to already built object");
			}
			List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
					.get(clazz) : null;
			if (configs == null) {
				configs = new ArrayList<SecurityConfigurer<O, B>>(1);
			}
			configs.add(configurer);
            //给configurers属性里放入以类名为Key,List<SecurityConfigurer<O, B>>为value
            //放入List<SecurityConfigurer<O, B>>里的是开发的自定义拦截器
			this.configurers.put(clazz, configs);
			if (buildState.isInitializing()) {
				this.configurersAddedInInitializing.add(configurer);
			}
		}
	}

这样所有的开发自定义的继承自WebSecurityConfigurer的配置类和SpringBootWebSecurityConfiguration配置类中通过@Bean方式纳入Spring容器管理的继承自WebSecurityConfigurer的WebSecurityConfigurerAdapter类都放到了AbstractConfiguredSecurityBuilder类的configurers属性中供后面调用。

即configurers属性值 = 开发自定的继承自WebSecurityConfigurer配置类+SpringBootWebSecurityConfiguration配置类导入的WebSecurityConfigurerAdapter类。

1.2 分析FilterChainProxy类型的代理对象是如何生成的,并且是如何管理springSecurityFilterChain 这个bean的。

继续分析springSecurityFilterChain方法里的webSecurity.build()方法。在前面我们知道了webSecurity对象是WebSecurity类型的。

	public final O build() throws Exception {
		if (this.building.compareAndSet(false, true)) {
            //关注此方法
			this.object = doBuild();
			return this.object;
		}
		throw new AlreadyBuiltException("This object has already been built");
	}
	@Override
	protected final O doBuild() throws Exception {
		synchronized (configurers) {
			buildState = BuildState.INITIALIZING;
            //待实现方法
			beforeInit();
            //调用的是AbstractConfiguredSecurityBuilder类里的init方法
			init();

			buildState = BuildState.CONFIGURING;
            //待实现方法
			beforeConfigure();
            //调用的是AbstractConfiguredSecurityBuilder类里的configure方法
			configure();

			buildState = BuildState.BUILDING;
            //调用的是WebSecurity类里的performBuild方法。
			O result = performBuild();

			buildState = BuildState.BUILT;

			return result;
		}
	}

 其中init方法调用了开发者自定义的实现了WebSecurityConfigurer

的配置类里的init方法及WebSecurityConfigurerAdapter类里的init方法。

以下是

	private void init() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.init((B) this);
		}

		for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
			configurer.init((B) this);
		}
	}
	public void init(final WebSecurity web) throws Exception {
        //生成一个HttpSecurity类型的对象
		final HttpSecurity http = getHttp();
        //addSecurityFilterChainBuilder对securityFilterChainBuilders属性赋值
		web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
			public void run() {
				FilterSecurityInterceptor securityInterceptor = http
						.getSharedObject(FilterSecurityInterceptor.class);
				web.securityInterceptor(securityInterceptor);
			}
		});
	}

 getHttp方法用于生成一个HttpSecurity类型的对象。

	protected final HttpSecurity getHttp() throws Exception {
		if (http != null) {
			return http;
		}

		DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
				.postProcess(new DefaultAuthenticationEventPublisher());
		localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);

		AuthenticationManager authenticationManager = authenticationManager();
		authenticationBuilder.parentAuthenticationManager(authenticationManager);
		Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects();

		http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
				sharedObjects);
		if (!disableDefaults) {
			// @formatter:off
			http
				.csrf().and()
				.addFilter(new WebAsyncManagerIntegrationFilter())
				.exceptionHandling().and()
				.headers().and()
				.sessionManagement().and()
				.securityContext().and()
				.requestCache().and()
				.anonymous().and()
				.servletApi().and()
				.apply(new DefaultLoginPageConfigurer<>()).and()
				.logout();
			// @formatter:on
			ClassLoader classLoader = this.context.getClassLoader();
            //SpringFactoriesLoader类的主要作用是通过类路径下的META-INF/spring.factories文件 
            //获取工厂类接口的实现类,初始化并保存在缓存中,以供Springboot启动过程中各个阶段的调用。
            //SpringFactoriesLoader.loadFactories():是根据参数factoryClass获取spring.factories下配置的所有实现类实例,返回List<T>的。
            //SpringFactoriesLoader.loadFactoryNames():是根据参数factoryClass获取spring.factories下配置
			List<AbstractHttpConfigurer> defaultHttpConfigurers =
					SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
            //将所有AbstractHttpConfigurer类型的对象放入到AbstractConfiguredSecurityBuilder类的configurers属性中供后面调用
			for(AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
				http.apply(configurer);
			}
		}
        //配置HttpSecurity类型对象的属性
		configure(http);
		return http;
	}

 其中configure方法调用了开发者自定义的继承自WebSecurityConfigurerAdapter的配置类里的configure方法

	private void configure() throws Exception {
		Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();

		for (SecurityConfigurer<O, B> configurer : configurers) {
			configurer.configure((B) this);
		}
	}

 其中performBuild调用的是WebSecurity类里的performBuild方法,该方法的作用是FilterChainProxy类型的代理对象,该代理对象将拦截器链纳入代理对象管理。

	@Override
	protected Filter performBuild() throws Exception {
		Assert.state(
				!securityFilterChainBuilders.isEmpty(),
				"At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. More advanced users can invoke "
						+ WebSecurity.class.getSimpleName()
						+ ".addSecurityFilterChainBuilder directly");
		int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
		List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
				chainSize);
		for (RequestMatcher ignoredRequest : ignoredRequests) {
			securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
		}
        //securityFilterChainBuilders取到的是HttpSecurity类型的对象
		for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
			securityFilterChains.add(securityFilterChainBuilder.build());
		}
        //重点关注此处,将拦截器链纳入filterChainProxy代理类管理,如果不扩展securityFilterChainBuilders属性,里面只有一个对象时HttpSecurity类型的。
		FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
		if (httpFirewall != null) {
			filterChainProxy.setFirewall(httpFirewall);
		}
		filterChainProxy.afterPropertiesSet();

		Filter result = filterChainProxy;
		if (debugEnabled) {
			logger.warn("\n\n"
					+ "********************************************************************\n"
					+ "**********        Security debugging is enabled.       *************\n"
					+ "**********    This may include sensitive information.  *************\n"
					+ "**********      Do not use in a production system!     *************\n"
					+ "********************************************************************\n\n");
			result = new DebugFilter(filterChainProxy);
		}
        //执行WebSecurityConfigurerAdapter类init方法里传入的 run方法,该方法用于
        //给filterSecurityInterceptor属性赋值   
		postBuildAction.run();
        返回的是FilterChainProxy 类型的对象
		return result;
	}

这样 springSecurityFilterChain这个Bean名实际上对应的是FilterChainProxy 类型的对象。

2. 生成一个DelegatingFilterProxy类型的对象.

分析spring.factories配置文件里的配置类SecurityFilterAutoConfiguration。

可以看到如下源码:

@Configuration
@ConditionalOnWebApplication(type = Type.SERVLET)
//加载SecurityProperties配置类
@EnableConfigurationProperties(SecurityProperties.class)
//AbstractSecurityWebApplicationInitializer和SessionCreationPolicy存在再执行配置
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
		SessionCreationPolicy.class })
//在SecurityAutoConfiguration加载完后再执行配置
@AutoConfigureAfter(SecurityAutoConfiguration.class)
public class SecurityFilterAutoConfiguration {
    //省略。。。。。
}

在该配置类中通过@Bean的方式往spring容器中注入了一个bean,该bean的类型是 DelegatingFilterProxyRegistrationBean。

	@Bean
	@ConditionalOnBean(name = DEFAULT_FILTER_NAME)
	public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
			SecurityProperties securityProperties) {
        //初始化对象,并对属性targetBeanName赋值为springSecurityFilterChain
		DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
				DEFAULT_FILTER_NAME);
		registration.setOrder(securityProperties.getFilter().getOrder());
		registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
		return registration;
	}

查看DelegatingFilterProxyRegistrationBean的继承实现树,发现该类实现了RegistrationBean。RegistrationBean中有一个onStartup方法,在SpringBoot启动的过程中,tomcat容器会调用该方法。

在onStartUp方法中存在register,根据动态绑定机制执行的是DynamicRegistrationBean里的register方法。

	@Override
	protected final void register(String description, ServletContext servletContext) {
        //重点关注
		D registration = addRegistration(description, servletContext);
		if (registration == null) {
			logger.info(StringUtils.capitalize(description) + " was not registered "
					+ "(possibly already registered?)");
			return;
		}
        //重点关注
		configure(registration);
	}

该register方法里调用了addRegistration方法。该addRegistration方法是一个抽象方法。调用的是AbstractFilterRegistrationBean里的addRegistration方法。

	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		return servletContext.addFilter(getOrDeduceName(filter), filter);
	}

 在AbstractFilterRegistrationBean中getFilter是调用的DelegatingFilterProxyRegistrationBean里的getFilter方法。在该方法中this.targetBeanName属性值是springSecurityFilterChain字符串。

	@Override
	public DelegatingFilterProxy getFilter() {
		return new DelegatingFilterProxy(this.targetBeanName,
				getWebApplicationContext()) {

			@Override
			protected void initFilterBean() throws ServletException {
				// Don't initialize filter bean on init()
			}

		};
	}

 分析getFilter方法该方法最终返回了一个DelegatingFilterProxy类型的对象。继续分析AbstractFilterRegistrationBean里的addRegistration方法。在addRegistration里执行完getFilter方法后,程序继续执行了servletContext.addFilter(getOrDeduceName(filter), filter)这段代码。在这段代码中

	@Override
	protected Dynamic addRegistration(String description, ServletContext servletContext) {
		Filter filter = getFilter();
		return servletContext.addFilter(getOrDeduceName(filter), filter);
	}

   一、servletContext是Tomcat里的ApplicationContextFacade类型的对象。

    public ServletContext getServletContext() {

        if (context == null) {
            context = new ApplicationContext(this);
            if (altDDName != null)
                context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
        }
        return (context.getFacade());

    }

二、ApplicationContextFacade通过addFilter方法往Tomcat的Servlet里添加了拦截器。这样拦截器就被Tomcat管理了。

    @Override
    public FilterRegistration.Dynamic addFilter(String filterName,
            Filter filter) {
        if (SecurityUtil.isPackageProtectionEnabled()) {
            return (FilterRegistration.Dynamic) doPrivileged("addFilter",
                    new Class[]{String.class, Filter.class},
                    new Object[]{filterName, filter});
        } else {
            //设置的filterName为springSecurityFilterChain,即FilterChainProxy类型
            //返回一个ApplicationFilterRegistration类型的对象
            
            return context.addFilter(filterName, filter);
        }
    }
    @Override
    public FilterRegistration.Dynamic addFilter(String filterName,
            Filter filter) {
        if (SecurityUtil.isPackageProtectionEnabled()) {
            return (FilterRegistration.Dynamic) doPrivileged("addFilter",
                    new Class[]{String.class, Filter.class},
                    new Object[]{filterName, filter});
        } else {
            return context.addFilter(filterName, filter);
        }
    }

其addFilter方法最终调用了以下ApplicationContext类里addFilter方法,该方法返回了一个

ApplicationFilterRegistration类型的对象。该对象的构造参数分别是filterDef和context

    private FilterRegistration.Dynamic addFilter(String filterName,
            String filterClass, Filter filter) throws IllegalStateException {

        if (filterName == null || filterName.equals("")) {
            throw new IllegalArgumentException(sm.getString(
                    "applicationContext.invalidFilterName", filterName));
        }

        if (!context.getState().equals(LifecycleState.STARTING_PREP)) {
            //TODO Spec breaking enhancement to ignore this restriction
            throw new IllegalStateException(
                    sm.getString("applicationContext.addFilter.ise",
                            getContextPath()));
        }

        FilterDef filterDef = context.findFilterDef(filterName);

        // Assume a 'complete' FilterRegistration is one that has a class and
        // a name
        if (filterDef == null) {
            filterDef = new FilterDef();
            filterDef.setFilterName(filterName);
            context.addFilterDef(filterDef);
        } else {
            if (filterDef.getFilterName() != null &&
                    filterDef.getFilterClass() != null) {
                return null;
            }
        }

        if (filter == null) {
            filterDef.setFilterClass(filterClass);
        } else {
            filterDef.setFilterClass(filter.getClass().getName());
            filterDef.setFilter(filter);
        }
        //重点关注此处,filterDef filtername是springSecurityFilterChain,该属性后面configure方法会使用

        return new ApplicationFilterRegistration(filterDef, context);
    }

继续回到DynamicRegistrationBean里的register方法。该方法里调用了configure方法,虽然因为调用onStartup方法的对象是DelegatingFilterProxyRegistrationBean,所以虽然DynamicRegistrationBean里有configure方法,实际上其调用的是AbstractFilterRegistrationBean里的register方法。该方法最终返回个ApplicationFilterRegistration类型的对象。

在configure方法中通过debug发现其通过registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter, DEFAULT_URL_MAPPINGS);

	protected void configure(FilterRegistration.Dynamic registration) {
		super.configure(registration);
		EnumSet<DispatcherType> dispatcherTypes = this.dispatcherTypes;
		if (dispatcherTypes == null) {
			dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
		}
		Set<String> servletNames = new LinkedHashSet<>();
		for (ServletRegistrationBean<?> servletRegistrationBean : this.servletRegistrationBeans) {
			servletNames.add(servletRegistrationBean.getServletName());
		}
		servletNames.addAll(this.servletNames);
		if (servletNames.isEmpty() && this.urlPatterns.isEmpty()) {
			this.logger.info("Mapping filter: '" + registration.getName() + "' to: "
					+ Arrays.asList(DEFAULT_URL_MAPPINGS));
            //重点关注此处
			registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
					DEFAULT_URL_MAPPINGS);
		}
		else {
			if (!servletNames.isEmpty()) {
				this.logger.info("Mapping filter: '" + registration.getName()
						+ "' to servlets: " + servletNames);
				registration.addMappingForServletNames(dispatcherTypes, this.matchAfter,
						StringUtils.toStringArray(servletNames));
			}
			if (!this.urlPatterns.isEmpty()) {
				this.logger.info("Mapping filter: '" + registration.getName()
						+ "' to urls: " + this.urlPatterns);
				registration.addMappingForUrlPatterns(dispatcherTypes, this.matchAfter,
						StringUtils.toStringArray(this.urlPatterns));
			}
		}
	}

 对TomcatEmbeddedContext类的filterMap属性进行赋值供后面调用。filterMap里放入的FilterName是springSecurityFilterChain。

    public void addMappingForUrlPatterns(
            EnumSet<DispatcherType> dispatcherTypes, boolean isMatchAfter,
            String... urlPatterns) {

        FilterMap filterMap = new FilterMap();
        //filterDef 是 ApplicationContext里赋值的

        filterMap.setFilterName(filterDef.getFilterName());

        if (dispatcherTypes != null) {
            for (DispatcherType dispatcherType : dispatcherTypes) {
                filterMap.setDispatcher(dispatcherType.name());
            }
        }

        if (urlPatterns != null) {
            // % decoded (if necessary) using UTF-8
            for (String urlPattern : urlPatterns) {
                filterMap.addURLPattern(urlPattern);
            }

            if (isMatchAfter) {
                context.addFilterMap(filterMap);
            } else {
                context.addFilterMapBefore(filterMap);
            }
        }
        // else error?

    }

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringSecurity源码分析(一) SpringBoot集成SpringSecurity即Spring安全框架的加载过程 的相关文章

  • 如何为带有未确定的“?”的Java通用Map添加值值类型?

    我在 JDK 8 示例中看到过这种声明 Map
  • 为 Nimbus 外观设计简单的单元渲染器

    我有一个简单的单元格渲染器 它由一些组成JLabels 渲染器本身扩展JPanel 并且我正在尝试让它在 Nimbus 的外观和感觉中合理地渲染 基本上发生的事情是在lighter行 正如 Nimbus 所具有的交替行着色 我的特定单元格渲
  • 如何通过双击图标来执行JAVA程序?

    我写了一个java程序 现在我想在没有 IDE Eclipse 等的情况下打开我的控制台 java 应用程序 只需双击桌面上的可执行版本即可 我已将 java 项目导出为 Runnable JAR 文件 但无法打开 当我尝试使用cmd打开应
  • Spring boot - 嵌入式 Tomcat - 连接器定制器 - 无法添加 parseBodyMethods 属性

    最初的问题是当我使用 DELETE 方法发送http请求时 正文部分无法发送到服务器 经过谷歌搜索后 我发现本文 https stackoverflow com questions 41205062 requestbody not acce
  • Gradle中的build-by-convention深度解释是什么?

    The 摇篮用户指南 http www gradle org docs current userguide userguide html经常提到 Gradle 是陈述性的和用途按惯例构建 这是什么意思 据我了解 这意味着 例如 在java插
  • Jersey 客户端异步 POST 请求不等待响应

    我创建了一个简单的 Jersey 客户端 它能够成功地使用有效负载执行 POST 请求 但现在它正在等待来自 http 端点的响应 public void callEndpoint String endpoint String payloa
  • 高负载应用程序的数据库可扩展性?

    我见过一些应用程序拥有集群 Web 服务器 例如 10 到 20 个服务器 以具有可扩展性 可以在其中分发 在网络服务器之间加载 但我总是看到所有网络服务器都使用单个数据库 现在考虑任何电子商务或铁路 Web 应用程序 其中有数百万用户在任
  • Spring Boot 删除 Whitelabel 错误页面

    我正在尝试删除白色标签错误页面 所以我所做的是为 error 创建了一个控制器映射 RestController public class IndexController RequestMapping value error public
  • Java Swing 自定义形状(2D 图形)

    我需要绘制自定义形状 现在 当用户单击面板上的几个点时 我使用多边形创建一个形状 public void mouseClicked MouseEvent e polygon addPoint e getX e getY repaint 但我
  • Bean 属性不可读或具有无效的 getter 方法

    因此 我的任务是为注册表路由编写一个简单的 Web 应用程序 使用 Spring MVC 所以我有 路线 类 我想在其中保留起点 终点和中间点列表 但我不明白如何将值从 jsp 放入列表 例如使用 jstl 所以我决定解析一个字符串 pub
  • 如何将多个值存储到一个键(java)

    我搜索一个可以存储多个键值对的数据结构 数据基本上是这样的 1 value 1 2 value 2 于是我想到了使用HashMap 遗憾的是 这对我不起作用 因为一个键可能会出现多个值 在上面的例子中 1 value 2 可能是另一个条目
  • 如何从网上获取源代码?

    我正在尝试从 Web 获取 HTML 源代码 我尝试这样做 u new URL url URLConnection con u openConnection con setRequestProperty User Agent Mozilla
  • Java检测音频文件(mp3)

    我有这段代码可以读取 mp3 文件 import java io File import java io IOException import javax sound sampled AudioSystem import javax sou
  • c3p0 Java 数据库池、故障转移配置

    当数据库关闭时 IP 和端口会自动切换到另一个数据库服务器 我应该如何配置 Web 应用程序的 c3p0 连接池以遵循此数据库故障转移机制 目前 我使用的是 c3p0 但是在上次数据库故障转移中 池连接无法重新建立 请求失败后重新建立 有助
  • Java 8 中函数类型全等 lambda 表达式的用法

    我对 的定义和用法感到困惑 Stream collect Supplier
  • 使用 GSON 将 JSON 字符串转换为 Java 对象

    我正在尝试将 json 解析为 java 根据 jsonlint com 我有以下字符串 该字符串是有效的 json private final static String LOC JSON lat1 39 737567 lat2 32 7
  • 使用 testcontainer 作为 Dockerfile 的一部分运行测试

    我的 dockerfile 看起来像这样 FROM maven 3 jdk 11 slim COPY pom xml COPY src src RUN mvn clean install 这意味着构建的一部分是单元测试的执行 一些单元测试使
  • java银行程序帐户ID不上去?

    每次创建银行帐户时 帐户 ID 都应增加 1 但每次我尝试提取 Id 时 我只会得到帐户 ID 为 0 任何建议 因为我完全按照我学习的书中的方式进行操作而且它仍然没有更新 帐户构造函数 public class BankAccount p
  • 将菜单添加到空活动

    我在 Android Studio 中制作了一个 Android 应用程序 并想在其上创建一个选项菜单 我将其创建为一个空活动 现在意识到我最好创建一个空白活动来获取选项菜单 无论如何 是否可以在空活动中创建选项菜单 如果有人能给我指出一个
  • 应用程序中 GC 长时间暂停

    我当前运行的应用程序需要最大堆大小为 16GB 目前我使用以下标志来处理垃圾收集 XX UseParNewGC XX UseConcMarkSweepGC XX CMSInitiatingOccupancyFraction 50 XX Di

随机推荐

  • Python 2.7下下载并安装nltk (自然语言处理工具包)

    1 在cmd窗口中 进入到python的文件夹内的 Scripts内 我的目录地址是 D using Python Scripts 命令行输入 easy install pip 运行结束后 安装PyYAML and NLTK 命令行输入 p
  • c++ 中bool 的默认值

    比如在Test h中定义变量 isFirst Test h头文件 ifndef TEST H define TEST H class Test private bool isFirst endif 然后访问它 include Test h
  • Java面试题整理——网络

    Java面试题整理 网络 网络 1 常用的 http 响应码及含义 200 OK 请求成功 这是对HTTP请求成功的标准应答 201 Created 请求创建被完成 同时新的资源被创建 202 Acceptd 供处理的请求已被接受 但是处理
  • 【VxWorks】Vxworks、QNX、Xenomai、Intime、Sylixos、Ucos等实时操作系统的性能特点

    目录 1 VxWorks操作系统 2 QNX操作系统 3 Xenomai操作系统 4 INtime操作系统 5 SylixOS操作系统 5 1 SylixOS官网
  • uniapp 跳转传参 [‘object‘] 问题解决, 遇坑解决

    普通 号拼接传参不知道什么原因 接收参数时转换数据失败 换成模板传参试试 解决 传 ckgd productList 传参时先转为json 一定要用模板字符串传参 如下 uni navigateTo url pagesA service d
  • MySQL执行器与存储引擎是怎么交互的

    体系结构 1 连接器 2 查询缓存 3 分析器 4 优化器 多个索引 选择哪个索引 join的顺序 5 执行器 调用存储引擎接口获取满足条件的第一行记录 调用存储引擎接口获取满足条件的下一行记录 6 存储引擎 索引下推 假设现子表T有字段
  • c++泛型算法扩展和迭代器、反向迭代器

    cout lt lt 插入迭代器 lt lt endl back inserter 创建一个使用push back的迭代器 front inserter 创建一个使用push front的迭代器 inserter 创建一个使用insert的
  • bee-box

    这篇博客就是为了记下bee box做题过程 随便记记 免得忘了 安装 先去官网下载了 然后分个新的盘单独放进去 打开虚拟机 双击bee box vmx就能安装了 打开里面的火狐会自动跳去一个登录界面 默认账号和密码是bee bug 登录然后
  • GitHub 源代码被泄露了...

    阅读本文大概需要 4 分钟 来自量子位 GitHub 忽然 开源 了自己代码的一部分 还将它放在了 GitHub 上 事件起因是这样的 TypeScript 的开发者 Resynth 忽然 Po 了篇文章 表示代码托管服务 GitHub 的
  • 【牛客网OJ题】不要二

    题目描述 二货小易有一个WH的网格盒子 网格的行编号为 0 H 1 网格的列编号为0 W 1 每个格子至多可以放一块蛋糕 任意两块蛋糕的欧几里得距离不能等于2 对于两个格子坐标 x1 y1 x2 y2 的欧几里得距离为 x1 x2 x1 x
  • spring注解:@Autowired、@Qualifier、@Primary

    Autowired 默认情况下 Autowired 按类型装配 Spring Bean 如果容器中有多个相同类型的 bean 则框架将抛出 NoUniqueBeanDefinitionException 以提示有多个满足条件的 bean 进
  • Raki的读paper小记:SELF-INSTRUCT: Aligning Language Models with Self-Generated Instructions

    Abstract Introduction Related Work 研究任务 改进大模型遵循指令的能力 SELF INSTRUCT提供了一种几乎无需注释的方法来使预训练语言模型与指令对齐 已有方法和相关工作 许多研究提出使用语言模型进行数
  • Vue 2.0双向绑定原理的实现

    Object defineProperty方法 vue js是采用数据劫持结合发布 订阅者模式的方式 通过Object defineProperty 来劫持各个属性的setter getter 在数据变动时发布消息给订阅者 触发相应的监听回
  • 分布式系统的正确性验证方法

    分布式系统的正确性验证方法 1 Jepsen框架 Jepsen是一个开源的分布式一致性验证框架 可用于验证分布式数据库 分布式消息队列 分布式协调系统 Jepsen探索特定故障模式下分布式系统是否满足一致性 Jepsen框架是一个
  • 用于构建 RESTful Web 服务的多层架构

    文章出自 Bruce Sun Java 架构师 IBM 简介 由于它简便 轻量级以及通过 HTTP 直接传输数据的特性 RESTful Web 服务成为基于 SOAP 服务的一个最有前途的替代方案 在本文中 我们将概述 REST 和 RES
  • 分享63个最常见的前端面试题及其答案

    在前端面试中 各种面试题都会遇到 因此 今天我们整理了60 比较常见繁杂的面试题 希望这些面试题能够对你有所帮助 当然 这些面试题的答案都不是标准答案 只是对答案做了一个简介明了的说明 希望可以快速帮助你梳理重点核心内容 这些答案可以作为参
  • 密码破解---实验八:Windows本地破解用户口令

    目录 一 实验目的及要求 二 实验原理 1 Windows NT 系统密码存储的基本原理 2 SAM的导出方法 三 实验环境 四 实验步骤及内容 五 实验总结 六 分析与思考 一 实验目的及要求 1 了解Windows2000 XP Ser
  • apache服务详解

    APACHE服务 Apache HTTP Server 简称Apache 是Apache软件基金会的一个开放源码的网页服务器 可以在大多数计算机操作系统中运行 由于其多平台和安全性被广泛使用 是最流行的Web服务器端软件之一 它快速 可靠并
  • [机缘参悟-72]:深度思考-人生自省的四重境界:不觉、自觉、觉他、圆满

    前言 人有觉 自觉 觉他 觉行圆满 阐述了人生自省的三重状态 把 不觉 也放入其中 作为在芸芸众生的起点 福 看天下 众生皆苦 从 福 家来看 身体的劳作都算不上真正的苦 福 学中 对于 苦 有不同的认识 可以总结出人世间八大痛苦 此为众生
  • SpringSecurity源码分析(一) SpringBoot集成SpringSecurity即Spring安全框架的加载过程

    Spring Security是一个强大的并且高度可定制化的访问控制框架 它基于spring应用 Spring Security是聚焦于为java应用提供授权和验证的框架 像所有的spring项目一样 Spring Security真正的强