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 EE:如何获取我的应用程序的 URL?

    在 Java EE 中 如何动态检索应用程序的完整 URL 例如 如果 URL 是 localhost 8080 myapplication 我想要一个可以简单地将其作为字符串或其他形式返回给我的方法 我正在运行 GlassFish 作为应
  • 如何找到给定字符串的最长重复子串

    我是java新手 我被分配寻找字符串的最长子字符串 我在网上研究 似乎解决这个问题的好方法是实现后缀树 请告诉我如何做到这一点或者您是否有任何其他解决方案 请记住 这应该是在 Java 知识水平较低的情况下完成的 提前致谢 附 测试仪字符串
  • 在 HTTPResponse Android 中跟踪重定向

    我需要遵循 HTTPost 给我的重定向 当我发出 HTTP post 并尝试读取响应时 我得到重定向页面 html 我怎样才能解决这个问题 代码 public void parseDoc final HttpParams params n
  • 无法展开 RemoteViews - 错误通知

    最近 我收到越来越多的用户收到 RemoteServiceException 错误的报告 我每次给出的堆栈跟踪如下 android app RemoteServiceException Bad notification posted fro
  • Android MediaExtractor seek() 对 MP3 音频文件的准确性

    我在使用 Android 时无法在eek 上获得合理的准确度MediaExtractor 对于某些文件 例如this one http www archive org download emma solo librivox emma 01
  • Liferay ClassNotFoundException:DLFileEntryImpl

    在我的 6 1 0 Portal 实例上 带有使用 ServiceBuilder 和 DL Api 的 6 1 0 SDK Portlet 这一行 DynamicQuery query DynamicQueryFactoryUtil for
  • 磁模拟

    假设我在 n m 像素的 2D 表面上有 p 个节点 我希望这些节点相互吸引 使得它们相距越远吸引力就越强 但是 如果两个节点之间的距离 比如 d A B 小于某个阈值 比如 k 那么它们就会开始排斥 谁能让我开始编写一些关于如何随时间更新
  • 在两个活动之间传输数据[重复]

    这个问题在这里已经有答案了 我正在尝试在两个不同的活动之间发送和接收数据 我在这个网站上看到了一些其他问题 但没有任何问题涉及保留头等舱的状态 例如 如果我想从 A 类发送一个整数 X 到 B 类 然后对整数 X 进行一些操作 然后将其发送
  • 使用Caliper时如何指定命令行?

    我发现 Google 的微型基准测试项目 Caliper 非常有趣 但文档仍然 除了一些示例 完全不存在 我有两种不同的情况 需要影响 JVM Caliper 启动的命令行 我需要设置一些固定 最好在几个固定值之间交替 D 参数 我需要指定
  • Java Integer CompareTo() - 为什么使用比较与减法?

    我发现java lang Integer实施compareTo方法如下 public int compareTo Integer anotherInteger int thisVal this value int anotherVal an
  • 在 Mac 上正确运行基于 SWT 的跨平台 jar

    我一直致力于一个基于 SWT 的项目 该项目旨在部署为 Java Web Start 从而可以在多个平台上使用 到目前为止 我已经成功解决了由于 SWT 依赖的系统特定库而出现的导出问题 请参阅相关thread https stackove
  • Eclipse Java 远程调试器通过 VPN 速度极慢

    我有时被迫离开办公室工作 这意味着我需要通过 VPN 进入我的实验室 我注意到在这种情况下使用 Eclipse 进行远程调试速度非常慢 速度慢到调试器需要 5 7 分钟才能连接到远程 jvm 连接后 每次单步执行断点 行可能需要 20 30
  • 使用Spring将war文件WEB-INF目录下的资源导入到applicationContext文件中

    我在我的项目中使用 Spring 框架 我想导入下面的所有 xml 资源 文件 WEB INF CustomerService spring integration Jobs applicationContext配置文件中war文件的目录
  • 无法捆绑适用于 Mac 的 Java 应用程序 1.8

    我正在尝试将我的 Java 应用程序导出到 Mac 该应用程序基于编译器合规级别 1 7 我尝试了不同的方法来捆绑应用程序 1 日食 我可以用来在 Eclipse 上导出的最新 JVM 版本是 1 6 2 马文 看来Maven上也存在同样的
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 获取 JVM 上所有引导类的列表?

    有一种方法叫做findBootstrapClass对于一个类加载器 如果它是引导的 则返回一个类 有没有办法找到类已经加载了 您可以尝试首先通过例如获取引导类加载器呼叫 ClassLoader bootstrapLoader ClassLo
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • JGit 检查分支是否已签出

    我正在使用 JGit 开发一个项目 我设法删除了一个分支 但我还想检查该分支是否已签出 我发现了一个变量CheckoutCommand但它是私有的 private boolean isCheckoutIndex return startCo
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • 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真正的强