Spring boot security 2.7.2


icon: edit
date: 2022-01-01
category:

  • CategoryA
    tag:
  • tag A
  • tag B

Spring boot security 2.7.2

吐槽

  • spring security项目已经在spring全家桶建起之初已经是spring的 之一了,其中的内部结构和设计原理都比较复杂,单从设计和实现的角度而言都已经劝退很多人了.
  • 网上的大多教程都只是皮毛,但起码也能跑跑程序完成老板的需求不是吗(不是,笔者刚开始接触它的时候也是一头雾水,抄抄网上的例子,勉勉强强的也能运行。。。
  • 这就是人家给你提供了技术,但是你 ,对比国内开发者可能更倾向于shiro,我以前也搞过shiro,的确shiro对比spring security较更为灵活(很正常,高度集成的缺点就是不灵活),但我到最后才发现spring security是真的牛逼…

简介

  • Spring Security是一个 、 的 和 框架。它是 的标准实现。
  • Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以很容易地扩展以满足定制需求。(源于各种 以及Bean装配

spring security 官方文档


准备

  • 本文档基于spring boot2.7.2

  • Spring Security下面简称 => security,该文档是传统的servlet实现。

  • maven坐标

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
        <version>2.7.2</version>
    </dependency>
    
  • starter引入了两个很重要的spring security的基础项目

        //该项目包含了security的基本实现,包括 认证 授权 debug ldap 全局方法访问控制 oauth2 saml2 等
    	<dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-config</artifactId>
          <version>5.7.2</version>
        </dependency>
        
    	//该项目包含上面提到的Web的授权验证实现
        <dependency>
          <groupId>org.springframework.security</groupId>
          <artifactId>spring-security-web</artifactId>
          <version>5.7.2</version>	
        </dependency>
    

自动配置

在sprongboot starter项目中,官方的starter项目 ,最终还是要看 spring-boot-autoconfigure项目的代码把相应的Bean注入到IOC的情况(如果我是初学者,这句话我大概率听不懂,什么鸟语,最后才发现年轻人还是太年轻了),如果你想看懂下面的代码的话,你需要对spring-framework 核心 && spring-boot自动配置 都需要一定的知识积累。

spring-boot-starter-security

 
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-security</artifactId>
      <version>2.7.2</version>
   </dependency>

				↓(包含)

	<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <version>2.7.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.3.22</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>			
      <version>5.7.2</version>
      <scope>compile</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>5.7.2</version>
      <scope>compile</scope>
   </dependency>

  • 首先spring-boot-starter-security包含的spring-security-configspring-security-web我就不多说了,上面提到过;它引入了spring-aop,为什么呢,小脑袋瓜想一想,尽然它引入了,就说明他用到了Aop,用Aop做什么呢,做 ,切什么的面?想想security可以对方法级别的权限控制,当然是可以切代码中方法切面啦,所以说基本上被在IOC的所有Bean的方法,理论上都可以被securit作为 ,进行 等功能,此Aop的精髓所在,他是实现 几乎所有项目的核心,是 实现的一种,被归为spring-framework的核心成员之一
  • 最后剩下了一个spring-boot-starter,偷偷的告诉你,他可是实现springboot 的核心哦,让我们一起去看看吧 , ,

spring-boot-starter

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter</artifactId>
  <version>2.7.2</version>
</dependency>
		↓(包含)

    <!--   该项目包含了spring-context spring-core,自身实现了springboot的基础-->
 	<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot</artifactId>
      <version>2.7.2</version>
      <scope>compile</scope>
    </dependency>
	  <!-- !!!!!! 自动配置实现的核心  !!!!-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-autoconfigure</artifactId>
      <version>2.7.2</version>
      <scope>compile</scope>
    </dependency>

      <!-- spring-bot自带的日志实现-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-logging</artifactId>
      <version>2.7.2</version>
      <scope>compile</scope>
    </dependency>

      <!-- 我不到这个干什么的 -->
    <dependency>
      <groupId>jakarta.annotation</groupId>
      <artifactId>jakarta.annotation-api</artifactId>
      <version>1.3.5</version>
      <scope>compile</scope>
    </dependency>

      <!-- spring-framework-core -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.3.22</version>
      <scope>compile</scope>
    </dependency>

      <!-- yaml文件的解析操作 -->
    <dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.30</version>
      <scope>compile</scope>
    </dependency>

前面几个都没啥,着重看看 spring-boot-autoconfigure

spring-boot-autoconfigure

spring-boot-autoconfigure项目的包结构很规整,都是按照项目名来命名的包结构,一目了然,是不是看到了我们熟悉的security

security实现了reactiveservlet两种web的实现,我们只看servlet实现。

对servlet的security自动配置

ConditionalOnDefaultWebSecurity

打开该包,我肯看到有一个注解:org.springframework.boot.autoconfigure.security.ConditionalOnDefaultWebSecurity

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {

}

class DefaultWebSecurityCondition extends AllNestedConditions {
	DefaultWebSecurityCondition() {
		super(ConfigurationPhase.REGISTER_BEAN);
	}
	@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
	static class Classes {}
	@ConditionalOnMissingBean({
			org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.class,
			SecurityFilterChain.class })
	@SuppressWarnings("deprecation")
	static class Beans {}
}

ConditionalOnDefaultWebSecurity该注解表明被该注解修饰的类只在当web安全可用且用户没有定义自己的配置时才匹配。

看源码,它是基于DefaultWebSecurityCondition实现的,条件是:

  • 类路径需要有 SecurityFilterChain HttpSecurity这两个类存在。
  • 当前spring-boot项目的 中没有WebSecurityConfigurerAdapterSecurityFilterChain这两个类型的Bean存在。

SecurityAutoConfiguration

spring-security自动配置的核心:org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration

@AutoConfiguration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({ SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class })
public class SecurityAutoConfiguration {

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

}
  • 该类被AutoConfiguration修饰,标识在spring-boot自动装配作业时会参加自动装配的工作。
  • 在类路径中有DefaultAuthenticationEventPublisher类时才会配置下面的类容。
  • 使类SecurityProperties读取配置文件的值并注入到 中。
  • SpringBootWebSecurityConfigurationSecurityDataConfiguration注入到 中。
  • 如果容器中缺少类型为AuthenticationEventPublisher的Bean,则注入该Bean。(该Bean可以接受订阅认证相关的事件,并在发生认证事件时发布(调用)给订阅者们)。

SpringBootWebSecurityConfiguration负责装配Web环境相关的Bean组件,SecurityDataConfiguration则是对数据层装配了一个类型为 SecurityEvaluationContextExtension的Bean(我还没研究过).

我们着重看看 SpringBootWebSecurityConfiguration 这个Bean又做了什么。

SpringBootWebSecurityConfiguration

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnDefaultWebSecurity
   static class SecurityFilterChainConfiguration {
      @Bean
      @Order(SecurityProperties.BASIC_AUTH_ORDER)
      SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
         http.authorizeRequests().anyRequest().authenticated();   //⑴
         http.formLogin();										  //⑵
         http.httpBasic();										  //⑶
         return http.build();									  //⑷
      }
   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnClass(WebInvocationPrivilegeEvaluator.class)
   @ConditionalOnBean(WebInvocationPrivilegeEvaluator.class)
   static class ErrorPageSecurityFilterConfiguration {
      @Bean
      FilterRegistrationBean<ErrorPageSecurityFilter> errorPageSecurityFilter(ApplicationContext context) {
         FilterRegistrationBean<ErrorPageSecurityFilter> registration = new FilterRegistrationBean<>(
               new ErrorPageSecurityFilter(context));
         registration.setDispatcherTypes(DispatcherType.ERROR);
         return registration;
      }
   }

   @Configuration(proxyBeanMethods = false)
   @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
   @ConditionalOnClass(EnableWebSecurity.class)
   @EnableWebSecurity
   static class WebSecurityEnablerConfiguration {}
}

其实也没有啥嘛,就是注入几个Bean嘛,,我们来分析一下:

  • 首先,@ConditionalOnWebApplication(type = Type.SERVLET)表示下面的一系列配置只在Servlet环境下生效。(没毛病,人家都说了是web)

  • 注入了一个名为 SecurityFilterChain的Bean,并做了如下几件事(这个Bean很重要,下文会细讲):

    ⑴ 任何http请求都需要认证处理。

    ⑵ 开启表单登录功能(别怀疑,就是html form的那个表单)

    ⑶ 开启http basic 认证 (我不大了解)

    ⑷ 构建类型为SecurityFilterChain的Bean,他的实际类型为:DefaultSecurityFilterChain

  • ErrorPageSecurityFilterConfiguration类构建了一个错误页面,就是出现安全相关的错误时会走这个Filter会拦截该请求并调用HttpServletResponse交由后续的Filter及其DispatcherServlet处理。

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws IOException, ServletException {
   Integer errorCode = (Integer) request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE);
   if (DispatcherType.ERROR.equals(request.getDispatcherType()) && !isAllowed(request, errorCode)) {
      response.sendError((errorCode != null) ? errorCode : 401);
      return;
   }
   chain.doFilter(request, response);
}
  • WebSecurityEnablerConfiguration其实就是默认给你标注了WebSecurity,就是当用户的类没有被该注解修饰时,他自动给你标注上,让这个注解配置的Bean生效,如果你做自己的类上用了该注解,那么也会应用该注解配置的Bean。(怎么觉得怪怪的 ,配没配都会生效)

不管了不管了,我们往下走,看看EnableWebSecurity

EnableWebSecurity

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
      HttpSecurityConfiguration.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
   boolean debug() default false;
}

好家伙,不看不知道,一看又配置了这么多的Bean,我们一个一个看吧(Oh,对了该注解有一个debug属性,把他设置成true的话他会打印所有跟认证授权相关的信息哦):

WebSecurityConfiguration

@Configuration(proxyBeanMethods = false)
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {

   private WebSecurity webSecurity;   //我们的认证主体

   private Boolean debugEnabled;     //上面说的debug会直影响这个属性

   private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; //配置所有安全Filter的基础

    //安全过滤链,默认引入security的话只会引入我们上面说的那个Bean,如果你还引入们oauth2相关,那么它还会引入一个oauth2的过滤器链
   private List<SecurityFilterChain> securityFilterChains = Collections.emptyList(); 

    //这是实现拓展的基础,用户通过实现WebSecurityCustomizer接口来配置WebSecurity(需要注入到IOC)
   private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();

   private ClassLoader beanClassLoader;

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

   @Bean
   public static DelegatingApplicationListener delegatingApplicationListener() {
      return new DelegatingApplicationListener();
   }

   //该Bean可以解析一些security或者其他的一些表达式,具体实现是由其实现类决定(默认注入的是SPEL解析类)
   @Bean
   @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
   public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
      return this.webSecurity.getExpressionHandler();
   }

   @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
   public Filter springSecurityFilterChain() throws Exception {
      boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();
      boolean hasFilterChain = !this.securityFilterChains.isEmpty();
      Assert.state(!(hasConfigurers && hasFilterChain),
            "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");
      if (!hasConfigurers && !hasFilterChain) {
         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);
      }
      return this.webSecurity.build();
   }

   //这个我不太懂,网上说是类似授权的判断(投票)
   @Bean
   @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
   public WebInvocationPrivilegeEvaluator privilegeEvaluator() {
      return this.webSecurity.getPrivilegeEvaluator();
   }

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

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

   @Autowired(required = false)
   void setWebSecurityCustomizers(List<WebSecurityCustomizer> webSecurityCustomizers) {
      this.webSecurityCustomizers = webSecurityCustomizers;
   }

   @Bean
   public static BeanFactoryPostProcessor conversionServicePostProcessor() {
      return new RsaKeyConversionServicePostProcessor();
   }

   @Override
   public void setImportMetadata(AnnotationMetadata importMetadata) {
      Map<String, Object> enableWebSecurityAttrMap = importMetadata
            .getAnnotationAttributes(EnableWebSecurity.class.getName());
      AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes.fromMap(enableWebSecurityAttrMap);
      this.debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
      if (this.webSecurity != null) {
         this.webSecurity.debug(this.debugEnabled);
      }
   }

   @Override
   public void setBeanClassLoader(ClassLoader classLoader) {
      this.beanClassLoader = classLoader;
   }

   private static class AnnotationAwareOrderComparator extends OrderComparator {

      private static final AnnotationAwareOrderComparator INSTANCE = new AnnotationAwareOrderComparator();

      @Override
      protected int getOrder(Object obj) {
         return lookupOrder(obj);
      }

      private static int lookupOrder(Object obj) {
         if (obj instanceof Ordered) {
            return ((Ordered) obj).getOrder();
         }
         if (obj != null) {
            Class<?> clazz = ((obj instanceof Class) ? (Class<?>) obj : obj.getClass());
            Order order = AnnotationUtils.findAnnotation(clazz, Order.class);
            if (order != null) {
               return order.value();
            }
         }
         return Ordered.LOWEST_PRECEDENCE;
      }

   }

}

SpringWebMvcImportSelector

class SpringWebMvcImportSelector implements ImportSelector {

   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      if (!ClassUtils.isPresent("org.springframework.web.servlet.DispatcherServlet", getClass().getClassLoader())) {
         return new String[0];
      }
      return new String[] {
            "org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration" };
   }

}

这个嘛看表面意思就是对 SpringWebMvc 进行了配置,这里他实现了ImportSelector接口,selectImports方法返回的就是需要注入的目标Bean;代码中首先判断了DispatcherServlet在不在类路径,如果不在就不配置了,如果在就配置org.springframework.security.config.annotation.web.configuration.WebMvcSecurityConfiguration这个类。

咱们先看看这个类:

class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware {

   private BeanResolver beanResolver;

   @Override
   @SuppressWarnings("deprecation")
   public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
      AuthenticationPrincipalArgumentResolver authenticationPrincipalResolver = new AuthenticationPrincipalArgumentResolver();
      authenticationPrincipalResolver.setBeanResolver(this.beanResolver); 
      argumentResolvers.add(authenticationPrincipalResolver); //⑴
      argumentResolvers
            .add(new org.springframework.security.web.bind.support.AuthenticationPrincipalArgumentResolver());//⑵
      CurrentSecurityContextArgumentResolver currentSecurityContextArgumentResolver = new CurrentSecurityContextArgumentResolver();
      currentSecurityContextArgumentResolver.setBeanResolver(this.beanResolver);
      argumentResolvers.add(currentSecurityContextArgumentResolver);//⑶
      argumentResolvers.add(new CsrfTokenArgumentResolver());//⑷
   }

   @Bean
   RequestDataValueProcessor requestDataValueProcessor() {
      return new CsrfRequestDataValueProcessor();
   }

   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      this.beanResolver = new BeanFactoryResolver(applicationContext.getAutowireCapableBeanFactory());
   }

}

我们看到这个类实现了WebMvcConfigurer接口,这个接口就是对webmvc进行DIY的一个接口呀,那我们看看他做了什么吧。

addArgumentResolvers方法传来了一个List,泛型为HandlerMethodArgumentResolver,这个接口实现了对方法里面注入参数的解析,什么意思呢,就是在springmvc里控制的方法你为什么能直接注入HttpServletResponse,HttpServletRequest的原因,因为这两个参数都已经被实现HandlerMethodArgumentResolver的方法解析过啦,感兴趣的可以看看他的好很多实现。

@RequestMapping("user")
fun getUser(httpServletResponse: HttpServletResponse, httpServletRequest: HttpServletRequest) {}

知道了这个我们来看看下面加了哪里个参数解析器吧:

这个参数解析器是对参数列表参数标注了AuthenticationPrincipal注解的参数进行解析;

其实我看了代码和⑴很像,作用差不多;

这个参数解析器是对参数列表参数标注了CurrentSecurityContext注解的参数进行解析(当前认证用户的信息);

这个参数解析器是对参数列表中类型为了CsrfToken的参数进行解析;

这里就是配置了支持spring security的参数支持,方便在方法中直接注入支持的参数,随用随注入,前提是你得支持。

OAuth2ImportSelector

这个就是对spring Oauth2的一些支持,这里有专门写spring Oauth2的文章。

HttpSecurityConfiguration

@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {

   //Bean前缀名字(好长....)
   private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration."; 

   //当前这个类的类型Bean名字
   private static final String HTTPSECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";

   //不清楚
   private ObjectPostProcessor<Object> objectPostProcessor;

   //身份验证管理器
   private AuthenticationManager authenticationManager;

   //身份验证配置
   private AuthenticationConfiguration authenticationConfiguration;

    //当前spring应用程序的实体
   private ApplicationContext context;

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

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

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

   @Autowired
   void setApplicationContext(ApplicationContext context) {
      this.context = context;
   }

   //这个就是构建前面认证的主体所在
   @Bean(HTTPSECURITY_BEAN_NAME)
   @Scope("prototype")
   HttpSecurity httpSecurity() throws Exception {
      WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(
            this.context);  //用委托实现了一个密码加密器
      AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
            this.objectPostProcessor, passwordEncoder); //身份验证管理器的构建器
      authenticationBuilder.parentAuthenticationManager(authenticationManager());
      HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
      // @formatter:off
      http						//withDefaults其实什么也没做,也就是说下面的用了这个的,就是什么也不配置
         .csrf(withDefaults())   //csrf配置
         .addFilter(new WebAsyncManagerIntegrationFilter()) //异步的servlet支持(虽然我搞不懂异步的servlet)
         .exceptionHandling(withDefaults())     // 异常的处理器,认证失败啊成功啊,权限验证失败什么的
         .headers(withDefaults())			   // 将Security头添加到响应中
         .sessionManagement(withDefaults())    //session的管理策略,在无状态的应用中,都是禁用session的
         .securityContext(withDefaults())      // 没看懂...
         .requestCache(withDefaults())		   //字面意思请求缓存(没看懂...)
         .anonymous(withDefaults())			//对匿名用户的配置
         .servletApi(withDefaults())		 // 没看懂...
         .apply(new DefaultLoginPageConfigurer<>());
      http.logout(withDefaults());         //登出的配置
      // @formatter:on
      applyDefaultConfigurers(http);
      return http;
   }

    //在这里默认实现是ProviderManager(别问我为什么知道,我Debug过)
   private AuthenticationManager authenticationManager() throws Exception {
      return (this.authenticationManager != null) ? this.authenticationManager
            : this.authenticationConfiguration.getAuthenticationManager();
   }

   //对所有继承了AbstractHttpConfigurer的类都应用上述HttpSecurity的配置
   private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
      ClassLoader classLoader = this.context.getClassLoader();
      List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
            .loadFactories(AbstractHttpConfigurer.class, classLoader);
      for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
         http.apply(configurer);
      }
   }

   private Map<Class<?>, Object> createSharedObjects() {
      Map<Class<?>, Object> sharedObjects = new HashMap<>();
      sharedObjects.put(ApplicationContext.class, this.context);
      return sharedObjects;
   }
}

好啦,自此spring security的自动配置就到此位置啦!

SecurityFilterAutoConfiguration

好啦,接下来该配置Filter的主场了,有人就会问了,你怎么知道这个类要配置?了解springboot底层的童鞋肯定就晓得自动配置类需要在jar包的下有一个名叫的文件,这个文件描述的org.springframework.boot.autoconfigure.EnableAutoConfiguration这个属性就是会自动配置的类。

这个类的全类名是:org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration

@Configuration(  proxyBeanMethods = false)
@ConditionalOnWebApplication( type = Type.SERVLET)   //只在Servlet环境下会生效如下配置
@EnableConfigurationProperties({SecurityProperties.class})  //开启会SecurityProperties属性的注入,并将该Bean加到IOC容器里
@ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class}) //类路径下需要有这两个class
@AutoConfigureAfter({SecurityAutoConfiguration.class})   //在SecurityAutoConfiguration配置类之后再配置(上面我们刚讲了这个类配置)
public class SecurityFilterAutoConfiguration {
    private static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

    public SecurityFilterAutoConfiguration() {
    }

    //DelegatingFilterProxy这个类可不得了,这是security认证授权的核心组织者,他将security的一个类型为FilterChainProxy进行了一个代理
    //FilterChainProxy是一个特殊的filter,他代理了一堆类型为SecurityFilterChain的过滤器链
    //每一个SecurityFilterChain都是可以说是一种认证方式,就好比密码登录和扫码登陆他两都是一种认证方式
    //每一个SecurityFilterChain下维护了一堆filter专门做认证
    //在这里DelegatingFilterProxyRegistrationBean这个Bean注册了一个DelegatingFilterProxy对象
    @Bean
    @ConditionalOnBean(  name = {"springSecurityFilterChain"} )
    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(SecurityProperties securityProperties) {
        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean("springSecurityFilterChain", new ServletRegistrationBean[0]);
        registration.setOrder(securityProperties.getFilter().getOrder());
        registration.setDispatcherTypes(this.getDispatcherTypes(securityProperties));
        return registration;
    }

    private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
        return securityProperties.getFilter().getDispatcherTypes() == null ? null : (EnumSet)securityProperties.getFilter().getDispatcherTypes().stream().map((type) -> {
            return DispatcherType.valueOf(type.name());
        }).collect(Collectors.toCollection(() -> {
            return EnumSet.noneOf(DispatcherType.class);
        }));
    }
}

DelegatingFilterProxy

::: tabs

@tab:active FilterChain

Alt

@tab DelegatingFilterProxy

Alt

@tab SecurityFilterChain

Alt

@tab Security Filters

Alt

@tab Multiple SecurityFilterChain

Alt

:::

自动配置总结

通过上述的源码,我们发现spring security为我们做了如下的事情:

  • 自动引入了Aop
  • 配置了一个HttpSecurity 实体
  • 配置了一个Spring security的核心DelegatingFilterProxy
  • 配置了一个拦截所有请求的SecurityFilterChain 来做security的认证
  • 配置了一个Servlet异常的异常处理
  • 配置了支持AuthenticationPrincipalCurrentSecurityContextCsrfToken参数解析支持
  • 配置了 支持
  • 配置了一个ProviderManager
  • 等等…

实战

WebSecurityConfigurerAdapter已弃用

   
//Deprecated
//Use a org.springframework.security.web.SecurityFilterChain Bean to configure HttpSecurity or a WebSecurityCustomizer Bean to //configure WebSecurity
@Order(100)
@Deprecated
public abstract class WebSecurityConfigurerAdapter implements WebSecurityConfigurer<WebSecurity> {
		//..........
}

该类已经标注弃用,似乎是从5.0开始的,具体记不清了,以往配置security都是继承该类,重写两个configure方法,现在我们来实现新的配置方式。

注释提到现在的配置方式:

  • 使用SecurityFilterChain配置HttpSecurity
  • 使用WebSecurityCustomizer配置WebSecurity

配置SecurityFilterChain

//这两个注解其实任意一个就可以
@Configuration
@EnableWebSecurity
class WebSecurityConfiguration  {

    @Bean
    @Throws(Exception::class )
    @Order(SecurityProperties.BASIC_AUTH_ORDER)
    fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        //配置HttpSecurity....  
        return http.build()
    }

}

::: info 注意

上文中我们提到了 SpringBootWebSecurityConfigurationSecurityFilterChainConditionalOnDefaultWebSecurity注解条件下才会注入IOC,但很显然我们这里手动配置了SecurityFilterChain,所以我们他就不会配置这个Bean,在复习一下这个注解ConditionalOnDefaultWebSecurity

class DefaultWebSecurityCondition extends AllNestedConditions {

	DefaultWebSecurityCondition() {
		super(ConfigurationPhase.REGISTER_BEAN);
	}

	@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
	static class Classes {}

	@ConditionalOnMissingBean({WebSecurityConfigurerAdapter.class,SecurityFilterChain.class })
	@SuppressWarnings("deprecation")
	static class Beans {}

}

:::

:::info HttpSecurity注入

上面我们看到了HttpSecurityConfiguration配置了一个多例的HttpSecurity,默认IOC只有一个,你注入没问题,当有多这个该类型的Bean的时候,这就会报错啦,所以你要明确我为什么这么写。

其次我们刚好拿到HttpSecurity实例对象,进行我们自定义的配置。

:::

配置HttpSecurity

:::info 配置HttpSecurity

@EnableWebSecurity
class WebSecurityConfiguration {

    @Bean
    @Throws(Exception::class)
    fun defaultSecurityFilterChain(http: HttpSecurity): SecurityFilterChain {
        http.authorizeRequests()
            //antMatchers 使用的是 AntPathMatcher类来做路径匹配的
            //匹配该路径不做认证授权
            .antMatchers("/user/**", "/book/**").permitAll()
            //匹配改路径的GET请求不做认证授权  
            .antMatchers(HttpMethod.GET, "/oauth2/**").permitAll()
            //匹配改路径的GET请求通过指定的spel表达式授权
            .antMatchers(HttpMethod.GET, "/kern/**").access("authentication.authenticated")
            //其余的请求一律需要认证   
            .anyRequest().authenticated()
            //资源授权管理器
            .accessDecisionManager(object : AccessDecisionManager {
                //在认证之后对资源授权的最后关卡,默认有三种实现
                //1.AffirmativeBased 如果其中人一个投票人对其授权,那么他就默认获得这个资源的访问权限,前期是设置了allowIfAllAbstainDecisions为true
                //2.ConsensusBased  投赞成票人如果大于反对票的人则授权(少数服从多数),如果一样则取决于allowIfEqualGrantedDeniedDecisions的配置
                //3.UnanimousBased 全部投赞成票才能访问该资源
                //一般我们自定义的话默认实现AbstractAccessDecisionManager ,它包含 decisionVoters(选民),allowIfAllAbstainDecisions
                //
                override fun decide(authentication: Authentication, `object`: Any, configAttributes: MutableCollection<ConfigAttribute>) = Unit
                override fun supports(attribute: ConfigAttribute) = true
                override fun supports(clazz: Class<*>): Boolean = true
            })
        
        //表单验证配置
        http.formLogin()
            .permitAll() //表单认证的所有路径不做认证授权
            .failureForwardUrl("/login/fail") //登陆失败将调用该接口
            .successForwardUrl("/login/success")//登陆成功将调用该接口
            .usernameParameter("ace-username") //登陆的form表单的用户名字段
            .passwordParameter("ace-password") //登陆的form表单的口令字段
            .loginProcessingUrl("/login")      //处理登录逻辑的接口地址
            .failureHandler { request, response, exception ->  }  //登陆失败后做什么 
            .successHandler { request, response, authentication ->  } //登陆成功后做什么

        //session策略   
        http
            .sessionManagement()
            .sessionConcurrency {
                it.expiredSessionStrategy { } //session过期时调用该方法
                it.maxSessionsPreventsLogin(true) //当同一个用户被登陆多次时是否阻止其登录
                it.expiredUrl("/login/expired") //session过期后重定向到该地址
                it.maximumSessions(1)  //控制一个用户最大的session数量 (同上一起配合)
            }
        
        http
            .authenticationManager(ProviderManager()) //配置认证的管理器(一般不用配置,默认配置的就是ProviderManager)
             //增加一个filter和制定filter同级,他两的调用顺序不确定   
            .addFilterAt({ servletRequest, servletResponse, filterChain -> }, UsernamePasswordAuthenticationFilter::class.java)
             //增加一个filter 位置源于该filter的 Order配置
            .addFilter { servletRequest, servletResponse, filterChain-> }
            //增加一个位于制定filter之前的filter,
            .addFilterBefore({ servletRequest, servletResponse, filterChain-> },UsernamePasswordAuthenticationFilter::class.java)
            //增加一个位于制定filter之后的filter,
            .addFilterAfter({ servletRequest, servletResponse, filterChain-> },UsernamePasswordAuthenticationFilter::class.java)
            .cors { it.disable() } //配置cors支持
            .csrf { it.disable() } //csrf
            .exceptionHandling {  } //配置授权认证出现异常时的处理
         
        
        return http.build()
    }

}

:::

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值