spring-security源码学习总结

news2024/11/26 22:23:22

由于SpringBoot 对 Security 的支持类均位于org.springframework.boot.autoconfigure.security包下,主要通过 SecurityAutoConfiguration 自动配置类和 SecurityProperties 属性配置来完成,所以需要下载springboot源码深入学习

在这里插入图片描述

SecurityAutoConfiguration

首先来看SecurityAutoConfiguration做了什么事情

package org.springframework.boot.autoconfigure.security.servlet;

@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
		SecurityDataConfiguration.class, ErrorPageSecurityFilterConfiguration.class })
public class SecurityAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
	public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
		return new DefaultAuthenticationEventPublisher(publisher);
	}
}

@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)

该注解的作用是当项目中存在某个类时才会使标有该注解的类或方法生效,此处表示,当前类被创建前,DefaultAuthenticationEventPublisher必须已经存在,本类中向spring容器中注入了一个DefaultAuthenticationEventPublisher类型的对象,内部实现就是spring的ApplicationEventPublisher,用于springsecurity各种权限时间的交互,如登陆失败,会发布一个事件,然后通知其它组件做出相应的响应

@EnableConfigurationProperties(SecurityProperties.class)

该注解使ConfigurationProperties注解生效,此处就是使SecurityProperties类上的ConfigurationProperties,导入配置类SecurityProperties,内部声明了user对象

@Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,SecurityDataConfiguration.class, ErrorPageSecurityFilterConfiguration.class })

导入四个配置类,主要看前两个

SpringBootWebSecurityConfiguration

用于注册默认的SecurityFilterChain,conditional注解前溯会在DefaultWebSecurityCondition中判断SecurityFilterChain和WebSecurityConfigurerAdapter两个都缺失或者HttpSecurity和SecurityFilterChain同时存在才会注册默认的SecurityFilterChain,由此我们可以通过编写一个WebSecurityConfigurerAdapter的继承类来自定义SpringSecurity。也就是说,继承了WebSecurityConfigurerAdapter就会创建SecurityFilterChain

WebSecurityEnablerConfiguration

WebSecurityEnablerConfiguration类上添加了@EnableWebSecurity注解,该注解上又通过@Import导入了WebSecurityConfiguration、SpringWebMvcImportSelector、OAuth2ImportSelector和HttpSecurityConfiguration,同时该注解上的@EnableGlobalAuthentication类上又通过@Import注解导入了AuthenticationConfiguration

HttpSecurityConfiguration

使用@Autowired注入了一些bean

提供Spring的生命周期支持,主要是用于创建对象并把bean注入到spring容器中。这里注入的是AutowireBeanFactoryObjectPostProcessor类的一个实例

@Autowired
	void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
		this.objectPostProcessor = objectPostProcessor;
	}

鉴权对象,这里注入的是ProviderManager的一个实例(有可能在该类初始化的时候容器中还不存在ProviderManager)

void setAuthenticationManager(AuthenticationManager authenticationManager) {
   this.authenticationManager = authenticationManager;
}

用于构建ProviderManager的一个配置类

@Autowired
void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
   this.authenticationConfiguration = authenticationConfiguration;
}

依赖注入一个AuthenticationConfiguration,该类创建了三个重要的Bean对象AuthenticationManagerBuild、InitializeUserDetailsBeanManagerConfigurer和InitializeAuthenticationProviderBeanManagerConfigurer,分别用来

  • 构建AuthenticationManager
  • 用来初始化spring.factories中的UserDetailsServiceAutoConfiguration,它的功能是提供默认的用户名和密码、
  • 初始化用户认证管理器(ProviderManager)
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype") // 每次获得bean都会生成一个新的对象
HttpSecurity httpSecurity() throws Exception {
   // 密码编码器
   WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
         this.context);
   // AuthenticationManager建造器
   AuthenticationManagerBuilder authenticationBuilder = new 			                                                                    WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor,      	                     passwordEncoder);
   // 获取AuthenticationManager(ProviderManager),设置为父AuthenticationManager,用于管理所有的AuthenticationProvider
   authenticationBuilder.parentAuthenticationManager(authenticationManager());
   // 创建一个HttpSecurity,相当于xml文件配置中的http名称空间
   // 将provider注入HttpSecurity中
   HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
   // @formatter:off
   http
      .csrf(withDefaults())
      .addFilter(new WebAsyncManagerIntegrationFilter())
      .exceptionHandling(withDefaults())
      .headers(withDefaults())
      .sessionManagement(withDefaults())
      .securityContext(withDefaults())
      .requestCache(withDefaults())
      .anonymous(withDefaults())
      .servletApi(withDefaults())
      .apply(new DefaultLoginPageConfigurer<>());
   http.logout(withDefaults());
   // @formatter:on
   return http;
}

WebSecurityConfiguration

WebSecurityConfiguration用来创建FilterChianProxy,它是Spring Security使用的核心。FilterChainProxy可以在客户端请求时用来确定SecurityFilterChain应该使用哪个

在这里插入图片描述

主要方法:

setFilterChainProxySecurityConfigurer

创建 WebSecurity 建造者对象,apply() 初始配置

注解@Value(“#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}”)用来获取所有的WebSecurityConfigurer,再注入随后创建的WebSecurity

@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
      @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers)
      throws Exception {
   this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
   if (this.debugEnabled != null) {
      this.webSecurity.debug(this.debugEnabled);
   }
   webSecurityConfigurers.sort(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) {
      this.webSecurity.apply(webSecurityConfigurer);
   }
   this.webSecurityConfigurers = webSecurityConfigurers;
}

setFilterChainProxySecurityConfigurer方法有两个参数,第一个参数 objectPostProcessor 是一个对象后置处理器,由于该方法有一个@Autowired注解,会自动查找需要注入的参数,所以objectPostProcessor参数会自动注入进来。需要注意的是,@Autowired注解的required属性为false,所以在方法参数注入的时候,有就注入,没有则忽略。required属性设置为false主要是针对第二个参数webSecurityConfigurers ,因为该参数的值是通过调用 autowiredWebSecurityConfigurersIgnoreParents 对象的 getWebSecurityConfigurers 方法获取的。 autowiredWebSecurityConfigurersIgnoreParents 对象也是在当前类中注入到 Spring 容器中的,我们来看一下它的 getWebSecurityConfigurers 方法:

public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
   List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
   Map<String, WebSecurityConfigurer> beansOfType = this.beanFactory.getBeansOfType(WebSecurityConfigurer.class);
   for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
      webSecurityConfigurers.add(entry.getValue());
   }
   return webSecurityConfigurers;
}

可以看到,在 getWebSecurityConfigurers方法中主要是通过调用 beanFactory.getBeansOfType 方法来获取 Spring 容器中所有的 WebSecurityConfigurer 实例,也就是开发者自定义的各种各样继承自WebSecurityConfigurerAdapter的配置类。如果开发者没有自定义任何配置类,那么这里获取到的就是前面所讲的SpringBootWebSecurityConfiguration 类中提供的默认配置类,将获取到的所有配置类实例放入webSecurityConfigurers集合中并返回。

继续看方法体,在该方法中,首先创建一个WebSecurity实例,创建出来之后去对象后置处理器中走一圈,这样就将webSecurity对象注册到Spring容器中了

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

接下来, 根据每一个配置类的@Order注解对webSecurityConfigurers集合中的所有配置类进行排序,因为一个配置类对应一个过滤器链,当请求到来后,需要先和哪个过滤器链进行匹配,这里必然存在一个优先级问题,所以如果开发者自定义了多个配置类,则需要通过@Order注解标记 多个配置类的优先级

webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);

排序完成后,进入到for循环中,检查是否存在优先级相等的配置类, 如果存在,则直接抛出异常

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;
}

最后再去遍历所有的配置类,调用webSecurity.apply方法将其添加到webSecurity父类中的configurers集合中(将来遍历该集合并分别调用配置类的init、configure和performBuild方法完成配置类的初始化操作)

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

这是setFiltetChainProxySecurityConfigurer方法的执行逻辑,该方法主要用来初始化 WebSecurity对象,同时收集到所有的自定义配置类。有了 WebSecurity对象和配置类,接下来就可以构建过滤器FilterChainProxy 了。我们来看一下 springSecurityFilterChain 方法

setFilterChains

自动注入所有的SecurityFilteChain

@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
   this.securityFilterChains = securityFilterChains;
}
springSecurityFilterChain

建造者 webSecurity 调用 build() 方法,开始构建 springSecurityFilterChain

build() 方法进去继续调用了 doBuild(),该方法依次调用各个配置器的 init()、configure() 方法,等配置到位后,开始执行 performBuild(),进行核心过滤器 FilterChainProxy 的创建

public Filter springSecurityFilterChain() throws Exception {
    //判断是否配置了webSecurityConfigurers
    boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
    //判断是否配置了securityFilterChains
    boolean hasFilterChain = !this.securityFilterChains.isEmpty();
    Assert.state(!(hasConfigurers && hasFilterChain),
				"Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
    if (!hasConfigurers && !hasFilterChain) {
        /*如果没有配置webSecurityConfigurers和securityFilterChain则创建
        WebSecurityConfigurerAdapterConfigurers在setFilterChainProxySecurityConfigurer()方法里配置*/
        WebSecurityConfigurerAdapter adapter = this.objectObjectPostProcessor
            .postProcess(new WebSecurityConfigurerAdapter() {
            });
        this.webSecurity.apply(adapter);
    }
    for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
        this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
        for (Filter filter : securityFilterChain.getFilters()) {
            if (filter instanceof FilterSecurityInterceptor) {
                this.webSecurity.securityInterceptor((FilterSecurityInterceptor) filter);
                break;
            }
        }
    }
    for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
        customizer.customize(this.webSecurity);
    }
    //调用build()方法创建SecurityFilterChainProxy
    return this.webSecurity.build();
webSecurity.build()

build点进去

在这里插入图片描述

在这里插入图片描述

WebSecurityConfigurerAdapter.init()

首先调用getHttp()创建HttpSecurity

protected final HttpSecurity getHttp() throws Exception {
   if (this.http != null) {
      return this.http;
   }
   AuthenticationEventPublisher eventPublisher = getAuthenticationEventPublisher();
   this.localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
   AuthenticationManager authenticationManager = authenticationManager();
   this.authenticationBuilder.parentAuthenticationManager(authenticationManager);
   Map<Class<?>, Object> sharedObjects = createSharedObjects();
   this.http = new HttpSecurity(this.objectPostProcessor, this.authenticationBuilder, sharedObjects);
   if (!this.disableDefaults) {
      applyDefaultConfiguration(this.http);
      ClassLoader classLoader = this.context.getClassLoader();
      List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
            .loadFactories(AbstractHttpConfigurer.class, classLoader);
      for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
         this.http.apply(configurer);
      }
   }
   // 这里会加载自定义继承WebSecurityConfigerAdapter的配置类
   configure(this.http);
   return this.http;
}

然后调用WebSecurity的addSecurityFilterChainBuilder,把HttpSecurity传过去

在这里插入图片描述

可以看到 HttpSecurity 的实例被加到 WebSecurity 的 securityFilterChainBuilders 属性中

在这里插入图片描述

init方法执行完,再来看performBuild方法

@Override
protected Filter performBuild() throws Exception {
   Assert.state(!this.securityFilterChainBuilders.isEmpty(),
         () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
               + "Typically this is done by exposing a SecurityFilterChain bean "
               + "or by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
               + "More advanced users can invoke " + WebSecurity.class.getSimpleName()
               + ".addSecurityFilterChainBuilder directly");
   int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
   // 创建一个名为 securityFilterChain 的 List
   List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
   // 定义一个存放RequestMatcher的集合,用于配置忽略url和认证的url,如自定义的WebSecurityConfigurerAdapter类中的configure方法中配置	      // antMatchers("/user/query").hasAnyRole("ADMIN")
   List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
   for (RequestMatcher ignoredRequest : this.ignoredRequests) {
      WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
            + ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
      SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
      securityFilterChains.add(securityFilterChain);
      requestMatcherPrivilegeEvaluatorsEntries
            .add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
   }
   // 遍历 securityFilterChainBuilders 中的HttpSecurity ,然后调用HttpSecurity 的 build() 方法,将返回值添加到 securityFilterChain 中
   for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
      SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
      securityFilterChains.add(securityFilterChain);
      requestMatcherPrivilegeEvaluatorsEntries
            .add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
   }
   if (this.privilegeEvaluator == null) {
      this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
            requestMatcherPrivilegeEvaluatorsEntries);
   }
   // 创建filterChainProxy
   FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
   if (this.httpFirewall != null) {
      filterChainProxy.setFirewall(this.httpFirewall);
   }
   if (this.requestRejectedHandler != null) {
      filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);
   }
   filterChainProxy.afterPropertiesSet();

   Filter result = filterChainProxy;
   if (this.debugEnabled) {
      this.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);
   }
   this.postBuildAction.run();
   return result;
}
  1. 通过上面源码可以看到:

    1. 先创建了一个名为 securityFilterChain 的 List
    2. 遍历 securityFilterChainBuilders 中的元素,然后调用元素中 build() 方法,将返回值添加到 securityFilterChain 中
    3. 最后将 securityFilterChain 放到 FilterChainProxy 中

    在上一步中 init 方法中可以知道 securityFilterChainBuilders 中的元素是 HttpSecurity,调用 HttpSecurity 的 build() 方法返回 SecurityFilterChain 实例,最后创建FilterChainProxy集合并放入SecurityFilterChain集合

    至此 SecurityFilterChain 和FilterChainProxy的初始化就完成了

AuthenticationConfiguration

简单的说,这个类的作用就是用来创建ProviderManager,ProviderManager是一个AuthenticationManager实现,用于管理所有AuthenticationProvider实现的一个管理器

首先可以看到该类通过@Import注解导入了ObjectPostProcessorConfiguration

在这里插入图片描述

ObjectPostProcessorConfiguration

该配置类向spring容器中注入了ObjectPostProcessor的实现类AutowireBeanFactoiyObjectPostProcessor,该实现类主要用来将一个对象注册到Spring容器中去,我们在其他配置类中所见到的ObjectPostProcessor 实例其实都是这里提供的

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ObjectPostProcessorConfiguration {

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

}

authenticationManager用于存储一个AuthenticationManager对象,构建这个对象就是当前配置类的主要功能了

private AuthenticationManager authenticationManager;

globalAuthConfigurers是存储全局配置的一个列表,三个默认的GlobalAuthenticationConfigurerAdapter实现会被注入到这个属性列表中,他们分别是:EnableGlobalAuthenticationAutowiredConfigurer(当前配置类的一个内部类)、InitializeAuthenticationProviderBeanManagerConfigurer(初始化全局的 AuthenticationProvider对象)、InitializeUserDetailsBeanManagerConfigurer(初始化全局的 AuthenticationProvider对象),这三个类都是在当前配置类中使用@Bean注解引入,然后通过@Autowired注解的setGlobalAuthenticationConfigurers()方法将他们注入到globalAuthConfigurers属性中

private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections.emptyList();

authenticationManagerBuilder()方法,这个方法创建一个DefaultPasswordEncoderAuthenticationManagerBuilder,它是AuthenticationManagerBuilder的一个实现类,这个类使用密码解码器的延时加载策略LazyPasswordEncoder,如果能从applicationContext中获取到AuthenticationEventPublisher,也会将AuthenticationEventPublisher这个事件发布器设置到AuthenticationManagerBuilder中,DefaultPasswordEncoderAuthenticationManagerBuilder作为AuthenticationManagerBuilder的实现类,最后会在getAuthenticationManager方法中被用来创建AuthenticationManager的实现类ProviderManager

@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
      ApplicationContext context) {
   LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
   AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
         AuthenticationEventPublisher.class);
   DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
         objectPostProcessor, defaultPasswordEncoder);
   if (authenticationEventPublisher != null) {
      result.authenticationEventPublisher(authenticationEventPublisher);
   }
   return result;
}

getAuthenticationManager 方法用来构建具体的 AuthenticationManager 对象,在该方法内部,会首先判断AuthenticationManager对象是否已经初始化,如果已经初始化,则直接 返回 AuthenticationManager 对象,否则就先从 Spring 容器中获取到 AuthenticationManagerBuilder对象。注意这里还多了一个AuthenticationManagerDelegator对象,这个主要是为了防止在初始化AuthenticationManager时进行无限递归,拿到authBuilder对象之后,接下来遍历 globalAuthConfigurers配置类集合(也就是第二点中所说的三个配置类),将配置类分别添加到authBuilder对象中,然后进行构建,最终将构建结果返回

public AuthenticationManager getAuthenticationManager() throws Exception {
   if (this.authenticationManagerInitialized) {
      return this.authenticationManager;
   }
   AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
   if (this.buildingAuthenticationManager.getAndSet(true)) {
      return new AuthenticationManagerDelegator(authBuilder);
   }
   for (GlobalAuthenticationConfigurerAdapter config : this.globalAuthConfigurers) {
      authBuilder.apply(config);
   }
   this.authenticationManager = authBuilder.build();
   if (this.authenticationManager == null) {
      this.authenticationManager = getAuthenticationManagerBean();
   }
   this.authenticationManagerInitialized = true;
   return this.authenticationManager;
}

如果开发者在自定义配置类中重写了 configure(AuthenticationManagerBuilder)方法,这里的全局AuthenticationManager对象将不会生效,而大部分情况下,开发者都会重写 configure(AuthenticationManagerBuilder)方法,如

在这里插入图片描述

AuthenticationManager、ProviderManager和AuthenticationProvider三者之间的关系

以UsernamePasswordAuthenticationFilter为例,看一下三者关系

img

从上图可以看出验证逻辑为:

1、在UsernamePasswordAuthenticationFilter的attemptAuthentication()方法中,调用AuthenticationManager进行认证

2、AuthenticationManager接收Authentication对象作为参数,并通过authenticate方法对其进行验证(实际由其实现类ProviderManager完成)

3、在ProviderManager的authenticate方法中,轮训成员变量List providers。该providers中如果有一个

AuthenticationProvider的supports函数返回true,那么就会调用该AuthenticationProvider的authenticate函数认证,如果认证成功则整个

认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider进行认证,只要有一个认证成功则为认证成功。

4、UsernamePasswordAuthenticationToken实现了Authentication,主要是将用户输入的用户名密码进行封装,并提供给

AuthenticationManager进行验证,验证成功后,返回一个认证成功的UsernamePasswordAuthenticationToken对象

总结

AuthenticationConfiguration会获取到容器中所有的GlobalAuthenticationConfigurerAdapter实现,然后创建一个默认的AuthenticationManagerBuilder(就是DefaultPasswordEncoderAuthenticationManagerBuilder),接着将所有GlobalAuthenticationConfigurerAdapter配置设置到DefaultPasswordEncoderAuthenticationManagerBuilder中,然后提供一个对外方法getAuthenticationManager(),这个方法中会获取到DefaultPasswordEncoderAuthenticationManagerBuilder,然后创建一个AuthenticationManager(就是ProviderManager),这就是AuthenticationConfiguration所做的事情,整体来说,AuthenticationConfiguration的作用主要体现在两方面:第一就是导入了 ObjectPostProcessorConfiguration 配置类;第二则是提供 了一个全局的 AuthenticationManager 对象

authenticationEventPublisher

SecurityAutoConfiguration类上的所有注解都跟完之后,再来看这个类唯一的一个方法authenticationEventPublisher,它向spring容器中注入了一个AuthenticationEventPublisher的实现类DefaultAuthenticationEventPublisher并添加了一系列的异常

@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
   return new DefaultAuthenticationEventPublisher(publisher);
}
public DefaultAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
   this.applicationEventPublisher = applicationEventPublisher;
   addMapping(BadCredentialsException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
   addMapping(UsernameNotFoundException.class.getName(), AuthenticationFailureBadCredentialsEvent.class);
   addMapping(AccountExpiredException.class.getName(), AuthenticationFailureExpiredEvent.class);
   addMapping(ProviderNotFoundException.class.getName(), AuthenticationFailureProviderNotFoundEvent.class);
   addMapping(DisabledException.class.getName(), AuthenticationFailureDisabledEvent.class);
   addMapping(LockedException.class.getName(), AuthenticationFailureLockedEvent.class);
   addMapping(AuthenticationServiceException.class.getName(), AuthenticationFailureServiceExceptionEvent.class);
   addMapping(CredentialsExpiredException.class.getName(), AuthenticationFailureCredentialsExpiredEvent.class);
   addMapping("org.springframework.security.authentication.cas.ProxyUntrustedException",
         AuthenticationFailureProxyUntrustedEvent.class);
   addMapping("org.springframework.security.oauth2.server.resource.InvalidBearerTokenException",
         AuthenticationFailureBadCredentialsEvent.class);
}

当我们自定义过滤器时就可以抛出这些异常

SecurityFilterAutoConfiguration

当 Spring Boot 项目启动后,SecurityFilterAutoConfiguration类会加载 DelegatingFilterProxyRegistrationBean注册过滤器,名字为 springSecurityFilterChain

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class })
// 加载完SecurityAutoConfiguration之后才会加载本类
@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.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
   }

}

DelegatingFilterProxyRegistrationBean

该类getFilter方法返回的是DelegatingFilterProxy

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

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

   };
}

DelegatingFilterProxy

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
      throws ServletException, IOException {

   // Lazily initialize the delegate if necessary.
   Filter delegateToUse = this.delegate;
   if (delegateToUse == null) {
      synchronized (this.delegateMonitor) {
         delegateToUse = this.delegate;
         if (delegateToUse == null) {
            WebApplicationContext wac = findWebApplicationContext();
            if (wac == null) {
               throw new IllegalStateException("No WebApplicationContext found: " +
                     "no ContextLoaderListener or DispatcherServlet registered?");
            }
            delegateToUse = initDelegate(wac);
         }
         this.delegate = delegateToUse;
      }
   }

   // Let the delegate perform the actual doFilter operation.
   invokeDelegate(delegateToUse, request, response, filterChain);
}

这里delegateToUse是FilterChainProxy对象,由此可知,DelegatingFilterProxy类通过 springSecurityFilterChain这个名称,得到了一个 FilterChainProxy过滤器,最终执行的是这个过滤器的 doFilter方法

在这里插入图片描述

FilterChainProxy

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   // 在 doFilter 方法中,正常来说,clearContext 参数每次都是 true,于是每次都先给 request 标记上 FILTER_APPLIED 属性,然后执行 		
   // doFilterInternal 方法去走过滤器,执行完毕后,最后在 finally 代码块中清除 SecurityContextHolder 中保存的用户信息,同时移除 request      // 中的标记
   // FILTER_APPLIED 变量用来标记过滤器是否已经执行过了
   boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
   if (!clearContext) {
      doFilterInternal(request, response, chain);
      return;
   }
   try {
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      doFilterInternal(request, response, chain);
   }
   catch (RequestRejectedException ex) {
      this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
   }
   finally {
      SecurityContextHolder.clearContext();
      request.removeAttribute(FILTER_APPLIED);
   }
}

跟进doFilterInternal方法

private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   // 将请求封装成一个FirewalledRequest对象,在封装的过程中也会判断请求是否合法
   FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);
   // 对响应进行封装
   HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);
   // 调用getFilters方法根据当前的请求,从filterChains中找到对应的过滤器链,然后由该过滤器链去处理请求
   List<Filter> filters = getFilters(firewallRequest);
   // 如果找出来的filters为null,或者集合中没有元素,那就说明当前请求不需要经过过滤器。直接执行chain.doFilter,这就又回到原生过滤器
   // 中去了。如果发生这种情况,那就是针对项目中的静态资源,如果我们配置了资源放行,如web.ignoring().antMatchers("/hello");,
   // 那么当你请求/hello接口时就会走到这里来,也就是说这个不经过Spring Security Filte
   if (filters == null || filters.size() == 0) {
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));
      }
      firewallRequest.reset();
      chain.doFilter(firewallRequest, firewallResponse);
      return;
   }
   if (logger.isDebugEnabled()) {
      logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));
   }
   // 如果过滤器不为空,构建虚拟的过滤器链
   VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
   // 执行virtualFilterChain的doFilter方法
   virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}

跟进virtualFilterChain

virtualFilterChain
private static final class VirtualFilterChain implements FilterChain {

   // 原生的过滤器链,也就是Web Filter
   private final FilterChain originalChain;

   // Spring Security中的过滤器链
   private final List<Filter> additionalFilters;

   // 当前请求
   private final FirewalledRequest firewalledRequest;

   // 过滤器链中过滤器的个数
   private final int size;

   // 过滤器链过滤时候的下标
   private int currentPosition = 0;

   private VirtualFilterChain(FirewalledRequest firewalledRequest, FilterChain chain,
         List<Filter> additionalFilters) {
      this.originalChain = chain;
      this.additionalFilters = additionalFilters;
      this.size = additionalFilters.size();
      this.firewalledRequest = firewalledRequest;
   }

   @Override
   public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
      // currentPosition == size表示过滤器链已经执行完毕,此时通过调用originalChain.doFilter进入到原生过滤器链中,同时也退出了 Spring 		  // Security 过滤器链。否则就从 additionalFilters 取出 Spring Security 过滤器链中的一个个过滤器,挨个调用 doFilter 方法
      if (this.currentPosition == this.size) {
         if (logger.isDebugEnabled()) {
            logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));
         }
         // Deactivate path stripping as we exit the security filter chain
         this.firewalledRequest.reset();
         this.originalChain.doFilter(request, response);
         return;
      }
      this.currentPosition++;
      Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
      if (logger.isTraceEnabled()) {
         logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),
               this.currentPosition, this.size));
      }
      nextFilter.doFilter(request, response, this);
   }

}

设计模式

spring security继承了spring源码中简洁高效的编码风格,大量使用了注解和设计模式,举例:

模板方法(钩子函数、回调函数)

如AbstractUserDetailsAuthenticationProvider类中定义了两个空方法additionalAuthenticationChecks和retrieveUser,这两个方法由DaoAuthenticationProvider实现

责任链模式

这个不多说,security玩的就是责任链

装饰器模式

如上面刚说的FireWalledRequest

观察者模式

如AuthenticationEventPublisher,当出现异常时会调用该类发布事件

建造者模式

如AuthenticationManagerBuild

适配器模式

如WebSecurityConfigurerAdapter

策略模式

如果定义多个过滤器链会通过@Order进行排序,内部重写了compare方法,这就是典型的策略模式

代理模式

典型的如DelegatingFilterProxy,代理了FilterChainProxy。Spring Security Filter并不是直接嵌入到 Web Filter中的,而是通过FilterChainProxy来统一管理 Spring Security Filter,FilterChainProxy本身则通过Spring提供的DelegatingFilterProxy代理过滤器嵌入到Web Filter之中

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/27340.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

云原生边缘设备解决方案Akri on k3s初体验

作者&#xff1a; 涂家英&#xff0c;SUSE 资深架构师&#xff0c;专注 Cloud-Native 相关产品和解决方案设计&#xff0c;在企业级云原生平台建设领域拥有丰富的经验。 写在前面 k3s 是 SUSE 推出的为物联网和边缘计算构建的经过认证的 Kubernetes 发行版&#xff0c;它可以帮…

指数函数及其导函数

目录前言指数函数的导函数指数函数导函数动图绘制参考文献前言 前面我们介绍了指数函数及其基本性质以及如何在笛卡尔直角坐标系下绘制静态的指数函数图像&#xff0c;这一节&#xff0c;我们将重点讨论一下指数函数的导函数以及导函数的动态表示&#xff0c;为方便起见&#…

大数据(9f)Flink富函数RichFunction

文章目录1、概述2、示例2.1、普通函数2.2、富函数2.2.1、获取富函数的运行时上下文3、源码截取3.1、RichFunction3.2、RuntimeContext1、概述 Rich Function&#xff0c;译名富函数&#xff0c;和普通函数相比&#xff0c;多了&#xff1a;生命周期&#xff08;open和close方法…

DGL学习笔记——第二章 消息传递范式

提示&#xff1a;DGL用户指南学习中 文章目录一、内置函数和消息传递API二、编写高效的消息传递代码总结消息传递是实现GNN的一种通用框架和编程范式。它从聚合与更新的角度归纳总结了多种GNN模型的实现。 假设节点 &#x1d463; 上的的特征为 &#x1d465;&#x1d463;∈ℝ…

Java(八)----多线程

1. 线程的基本概念 1.1 进程 任何的软件存储在磁盘&#xff08;硬盘&#xff09;中,运行软件的时候,OS&#xff08;操作系统&#xff09;使用IO技术,将磁盘中的软件的文件加载到内存,程序才能运行。 &#xff08;进程是从硬盘到内存&#xff09; 进程的概念 &#xff1a; 应…

Marked.js让您的文档编辑更加轻松自如!

低代码应用平台——kintone既可以保留更改记录&#xff0c;也有流程管理的功能&#xff0c;在公司内部分享会议记录啊、wiki等文档或学习资料等时非常的便利。 kintone还有丰富的文本编辑框&#xff0c;可以对内容进行编辑提高易读性。但是还是有不少人觉得如果能够使用Markdo…

19.[Python GUI] PyQt5中的模型与视图框架-基本原理

PyQt中的模型与视图框架 一、Qt中模型与视图相关的类 二、模型与视图的基本原理 MVC把图形界面分为三个部分&#xff1a;模型&#xff08;Model&#xff09;&#xff0c;视图&#xff08;View&#xff09;和控制器&#xff08;Controller&#xff09;&#xff0c; 模型&#x…

Git大型文件存储

什么是 Git LFS&#xff1f; Git 是跟踪代码库演变和与同行高效协作的绝佳选择。但是&#xff0c;当您要跟踪的存储库非常大时会发生什么&#xff1f; 如果您考虑一下&#xff0c;存储库变得庞大的主要原因大致有两个&#xff1a; 他们积累了非常非常长的历史&#xff08;项目…

【C】文件操作fopen与fclose

目录 函数 1.fopen 2.fclose 3.freopen 函数 头文件 #include<stdio.h> 1.fopen FILE *fopen(const char *restrict dilename,const char* restrict mode); 作用&#xff1a;打开文件参数&#xff1a; 第一个是含有要打开文件名的字符串&#xff08;"文件名…

肝了一周的八万字Redis实战篇

Redis实战篇 文章目录Redis实战篇开篇导读1. 短信登录2. 商户查询缓存3. 优惠卷秒杀4. 附近的商户5. UV统计6. 用户签到7. 好友关注8. 达人探店一、短信登录1. 导入黑马点评项目1.1 导入SQL1.2 有关当前模型1.3 导入后端项目1.4 导入前端工程1.5 运行前端项目2. 基于Session实现…

【杂谈】快来看看如何使用LGMT这样的蜜汁缩写来进行CodeReview吧!

文章目录一、先从一个梗开始说起吧&#xff01;二、什么是LGTM&#xff1f;2.1 LGTM 是什么意思&#xff1f;2.2 蹭梗品牌故事2.3 虚假的CodeReview三、Code Review中的蜜汁缩写四、参考链接一、先从一个梗开始说起吧&#xff01; 公司最近在如火如荼的开展CodeReview活动&…

Reinforcement learning from demonstration through shaping(Wiewiora 2003)

摘要 强化学习中的一个重要问题是如何以有原则的方式整合专家知识&#xff0c;尤其是当我们扩展到现实世界的任务时。在本文中&#xff0c;我们提出了一种在不改变最优策略的情况下将任意建议纳入强化学习agent的奖励结构的方法。 该方法将 Ng 等人 (1999) 提出的基于势能的塑…

1530_AURIX_TriCore内核架构_通用寄存器以及系统寄存器

全部学习汇总&#xff1a; GreyZhang/g_tricore_architecture: some learning note about tricore architecture. (github.com) 继续看一下内核手册&#xff0c;这次了解一下通用寄存器以及系统寄存器。最近一段时间最复位以及trap困扰了许久&#xff0c;看看这里面是否能够获取…

黄河水稻山东智慧 国稻种芯·中国水稻节:济南泉城米袋子

黄河水稻山东智慧 国稻种芯中国水稻节&#xff1a;济南泉城米袋子 新闻中国采编网 中国新闻采编网 谋定研究中国智库网 中国农民丰收节国际贸易促进会 国稻种芯中国水稻节 中国三农智库网-功能性农业农业大健康大会报道&#xff1a;又是一年春天。济南黄河流域吴家堡水稻田旁的…

数据挖掘与机器学习:数据挖掘算法原理与实践:数据预处理

目录 第一关&#xff1a;标准化 任务描述&#xff1a; 相关知识&#xff1a; 一、为什么要进行标准化 二、Z-score标准化 三、Min-max标准化 四、MaxAbs标准化 编程要求&#xff1a; 测试说明&#xff1a; 第二关&#xff1a;非线性转换 任务描述&#xff1a; 相关知…

【LeetCode】878. 第 N 个神奇数字

题目描述 一个正整数如果能被 a 或 b 整除&#xff0c;那么它是神奇的。 给定三个整数 n , a , b &#xff0c;返回第 n 个神奇的数字。因为答案可能很大&#xff0c;所以返回答案 对 109 7 取模 后的值。 示例 1&#xff1a; 输入&#xff1a;n 1, a 2, b 3 输出&#xff…

stm32cubemx hal学习记录:FreeRTOS互斥量

一、互斥量 1、互斥量用于互锁&#xff0c;可以充当资源保护的令牌&#xff0c;当一个任务希望访问某个资源时&#xff0c;它必须先获取令牌&#xff0c;当任务使用完资源后&#xff0c;必须返还令牌&#xff0c;以便其他任务可以访问该资源。 2、互斥量一般用于临界资源保护…

[附源码]计算机毕业设计JAVA教师档案管理系统

[附源码]计算机毕业设计JAVA教师档案管理系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybati…

LabVIEW通信-CAN

文章目录CANcan总线特点位定位与同步标识符检验滤波报文传输类型CAN CAN属于OSI的物理层与数据链路层 can总线特点 网络各节点之间的数据通信实时性强 开发周期短 结构简单&#xff08;只有两根线与外部相连&#xff0c;内部继承了错误探测和管理模块&#xff09; 数据通信没…

Spring IOC

一、为什么要使用Spring&#xff1f; Spring 是个java企业级应用的开源开发框架。Spring主要用来开发Java应用&#xff0c;但是有些扩展是针对构建J2EE平台的web应用。Spring 框架目标是简化Java企业级应用开发&#xff0c;并通过POJO为基础的编程模型促进良好的编程习惯。 为…