转载

Spring Security 实战:Spring Boot 下的自动配置

点击上方蓝色“ 程序猿DD ”,选择“设为星标”

回复“ 资源 ”获取独家整理的学习资料!

来源 |  公众号「码农小胖哥」

1. 前言

我们在前几篇对 Spring Security 的用户信息管理机制,密码机制进行了探讨。 没有看的同学可通过 https://felord.cn 获取。 我们发现  Spring Security Starter 相关的  Servlet  自动配置都在 spring-boot-autoconfigure-2.1.9.RELEASE (当前 Spring Boot 版本为 2.1.9.RELEASE ) 模块的路径 org.springframework.boot.autoconfigure.security.servlet  之下。 其实官方提供的Starter组件的自动配置你都能在 spring-boot-autoconfigure-2.1.9.RELEASE 下找到。 今天我们进一步来解密  Spring Security  在  Spring Boot  的配置和使用。

2. Spring Boot 下 Spring Security 的自动配置

我们可以通过  org.springframework.boot.autoconfigure.security.servlet  路径下找到  Spring Security  关于 Servlet 的自动配置类。 我们来大致了解一下。

2.1 SecurityAutoConfiguration

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

  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
  import org.springframework.boot.autoconfigure.security.SecurityDataConfiguration;
  import org.springframework.boot.autoconfigure.security.SecurityProperties;
  import org.springframework.boot.context.properties.EnableConfigurationProperties;
  import org.springframework.context.ApplicationEventPublisher;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.Import;
  import org.springframework.security.authentication.AuthenticationEventPublisher;
  import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;

  /**
   * {@link EnableAutoConfiguration Auto-configuration} for Spring Security.
   *
   * @author Dave Syer
   * @author Andy Wilkinson
   * @author Madhura Bhave
   * @since 1.0.0
   */
  @Configuration
  @ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
  @EnableConfigurationProperties(SecurityProperties.class)
  @Import({ SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
          SecurityDataConfiguration.class })
  public class SecurityAutoConfiguration {

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

  }

SecurityAutoConfiguration  顾名思义安全配置类。 该类引入( @import )了  SpringBootWebSecurityConfiguration WebSecurityEnablerConfiguration  和  SecurityDataConfiguration  三个配置类。   让这三个模块的类生效。 是一个复合配置,是 Spring Security 自动配置最重要的一个类之一。   Spring Boot 自动配置经常使用这种方式以达到灵活配置的目的,这也是我们研究 Spring Security 自动配置的一个重要入口 同时  SecurityAutoConfiguration  还将  DefaultAuthenticationEventPublisher  作为默认的  AuthenticationEventPublisher  注入  Spring IoC  容器。 如果你熟悉 Spring 中的事件机制你就会知道该类是一个 Spring 事件发布器。 该类内置了一个 HashMap<String, Constructor<? extends AbstractAuthenticationEvent>> 维护了认证异常处理和对应异常事件处理逻辑的映射关系,比如账户过期异常  AccountExpiredException  对应认证过期事件 AuthenticationFailureExpiredEvent  ,也就是说发生不同认证的异常使用不同处理策略。

2.2 SpringBootWebSecurityConfiguration

@Configuration
  @ConditionalOnClass(WebSecurityConfigurerAdapter.class)
  @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
  @ConditionalOnWebApplication(type = Type.SERVLET)
  public class SpringBootWebSecurityConfiguration {

      @Configuration
      @Order(SecurityProperties.BASIC_AUTH_ORDER)
      static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {

      }

  }

这个类是Spring Security 对 Spring Boot Servlet Web 应用的默认配置。 核心在于 WebSecurityConfigurerAdapter  适配器。 从  @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)  我们就能看出  WebSecurityConfigurerAdapter  是安全配置的核心。   默认情况下  DefaultConfigurerAdapter  将以 SecurityProperties.BASIC_AUTH_ORDER -5 ) 的顺序注入 Spring IoC 容器,这是个空实现。 如果我们需要个性化可以通过继承  WebSecurityConfigurerAdapter  来实现。 我们会在以后的博文重点介绍该类。

2.3 WebSecurityEnablerConfiguration

@Configuration
  @ConditionalOnBean(WebSecurityConfigurerAdapter.class)
  @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
  @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
  @EnableWebSecurity
  public class WebSecurityEnablerConfiguration {

  }

该配置类会在 SpringBootWebSecurityConfiguration  注入 Spring IoC 容器后启用  @EnableWebSecurity  注解。 也就是说  WebSecurityEnablerConfiguration  目的仅仅就是在某些条件下激活  @EnableWebSecurity  注解。 那么这个注解都有什么呢?

3. @EnableWebSecurity 注解

@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
  @Target(value = { java.lang.annotation.ElementType.TYPE })
  @Documented
  @Import({ WebSecurityConfiguration.class,
          SpringWebMvcImportSelector.class,
          OAuth2ImportSelector.class })
  @EnableGlobalAuthentication
  @Configuration
  public @interface EnableWebSecurity {

      /**
       * Controls debugging support for Spring Security. Default is false.
       * @return if true, enables debug support with Spring Security
       */
      boolean debug() default false;
  }

@Enable*  这类注解都是带配置导入的注解。 通过导入一些配置来启用一些特定功能。   @EnableWebSecurity  导入了  WebSecurityConfiguration  、 SpringWebMvcImportSelector  、 OAuth2ImportSelector  以及启用了  @EnableGlobalAuthentication 注解。

3.1 WebSecurityConfiguration

该配置类 WebSecurityConfiguration 使用一个 WebSecurity 对象基于用户指定的或者默认的安全配置,你可以通过继承  WebSecurityConfigurerAdapter  或者实现  WebSecurityConfigurer  来定制  WebSecurity  创建一个 FilterChainProxy  Bean来对用户请求进行安全过滤。 这个 FilterChainProxy 的名称就是  WebSecurityEnablerConfiguration 上的  BeanIds.SPRING_SECURITY_FILTER_CHAIN  也就是  springSecurityFilterChain ,它是一个Filter,最终会被作为Servlet过滤器链中的一个Filter应用到Servlet容器中。 安全处理的策略主要是过滤器的调用顺序。 WebSecurityConfiguration  最终会通过  @EnableWebSecurity  应用到系统。

源码分析:

package org.springframework.security.config.annotation.web.configuration;

  import java.util.Collections;
  import java.util.List;
  import java.util.Map;

  import javax.servlet.Filter;

  import org.springframework.beans.factory.BeanClassLoaderAware;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.DependsOn;
  import org.springframework.context.annotation.ImportAware;
  import org.springframework.core.OrderComparator;
  import org.springframework.core.Ordered;
  import org.springframework.core.annotation.AnnotationAttributes;
  import org.springframework.core.annotation.AnnotationUtils;
  import org.springframework.core.annotation.Order;
  import org.springframework.core.type.AnnotationMetadata;
  import org.springframework.security.access.expression.SecurityExpressionHandler;
  import org.springframework.security.config.annotation.ObjectPostProcessor;
  import org.springframework.security.config.annotation.SecurityConfigurer;
  import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
  import org.springframework.security.config.annotation.web.builders.WebSecurity;
  import org.springframework.security.context.DelegatingApplicationListener;
  import org.springframework.security.web.FilterChainProxy;
  import org.springframework.security.web.FilterInvocation;
  import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
  import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;


  /**
   * Spring Web Security 的配置类 :
   *  1. 使用一个 WebSecurity 对象基于安全配置创建一个 FilterChainProxy 对象来对用户请求进行安全过滤。
   *  2. 也会暴露诸如 安全SpEL表达式处理器 SecurityExpressionHandler 等一些类。
   *
   * @see EnableWebSecurity
   * @see WebSecurity
   *
   * @author Rob Winch
   * @author Keesun Baik
   * @since 3.2
   */
  @Configuration
  public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
      private WebSecurity webSecurity;
   // 是否启用了调试模式,来自注解 @EnableWebSecurity 的属性 debug,缺省值 false
      private Boolean debugEnabled;

      private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;

      private ClassLoader beanClassLoader;

      @Autowired(required = false)
      private ObjectPostProcessor<Object> objectObjectPostProcessor;
    /**
     *
     * 代理监听器 应该时监听 DefaultAuthenticationEventPublisher 的一些处理策略
     */   
      @Bean
      public static DelegatingApplicationListener delegatingApplicationListener() {
          return new DelegatingApplicationListener();
      }
     /**
      *
      * 安全SpEL表达式处理器 SecurityExpressionHandler 缺省为一个 DefaultWebSecurityExpressionHandler
      */   
      @Bean
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler() {
          return webSecurity.getExpressionHandler();
      }

      /**
       *  Spring Security 核心过滤器  Spring Security Filter Chain  , Bean ID 为 springSecurityFilterChain
       * @return the {@link Filter} that represents the security filter chain
       * @throws Exception
       */
      @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public Filter springSecurityFilterChain() throws Exception {
          boolean hasConfigurers = webSecurityConfigurers != null
                  && SecurityConfigurers.isEmpty();
          if (!hasConfigurers) {
              WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor
                      .postProcess(new WebSecurityConfigurerAdapter() {
                      });
              webSecurity.apply(adapter);
          }
          return webSecurity.build();
      }

      /**
     *
     * 用于模板 如JSP Freemarker 的一些页面标签按钮控制支持
       * Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
       * tag support.
       * @return the {@link WebInvocationPrivilegeEvaluator}
       * @throws Exception
       */
      @Bean
      @DependsOn(AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
      public WebInvocationPrivilegeEvaluator privilegeEvaluator() throws Exception {
          return webSecurity.getPrivilegeEvaluator();
      }

      /**
     *
     * 用于创建web configuration的SecurityConfigurer实例,
     * 注意该参数通过@Value(...)方式注入,对应的bean autowiredWebSecurityConfigurersIgnoreParents
     * 也在该类中定义
     *
       * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a
       * {@link WebSecurity} instance
       * @param webSecurityConfigurers the
       * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to
       * create the web configuration
       * @throws Exception
       */
      @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);
          }

          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);
          }
          this.webSecurityConfigurers = webSecurityConfigurers;
      }
     /**
      * 从当前bean容器中获取所有的WebSecurityConfigurer bean。
      * 这些WebSecurityConfigurer通常是由开发人员实现的配置类,并且继承自WebSecurityConfigurerAdapter
      *
      */   
      @Bean
      public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents(
              ConfigurableListableBeanFactory beanFactory){
          return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory);
      }

      /**
       * A custom verision of the Spring provided AnnotationAwareOrderComparator that uses
       * {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class
       * instances for the {@link Order} annotation.
       *
       * @author Rob Winch
       * @since 3.2
       */
      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;
          }
      }

      /*
       * 要是为了获取注解 @EnableWebSecurity 的属性 debugEnabled
       *
       * @see org.springframework.context.annotation.ImportAware#setImportMetadata(org.
       * springframework.core.type.AnnotationMetadata)
       */
      public void setImportMetadata(AnnotationMetadata importMetadata) {
          Map<String, Object> enableWebSecurityAttrMap = importMetadata
                  .getAnnotationAttributes(EnableWebSecurity.class.getName());
          AnnotationAttributes enableWebSecurityAttrs = AnnotationAttributes
                  .fromMap(enableWebSecurityAttrMap);
          debugEnabled = enableWebSecurityAttrs.getBoolean("debug");
          if (webSecurity != null) {
              webSecurity.debug(debugEnabled);
          }
      }

      /*
       * (non-Javadoc)
       *
       * @see
       * org.springframework.beans.factory.BeanClassLoaderAware#setBeanClassLoader(java.
       * lang.ClassLoader)
       */
      public void setBeanClassLoader(ClassLoader classLoader) {
          this.beanClassLoader = classLoader;
      }
  }

3.2 SpringWebMvcImportSelector

该类是为了对 Spring Mvc 进行支持的。 一旦发现应用使用 Spring Mvc 的核心前置控制器  DispatcherServlet  就会引入  WebMvcSecurityConfiguration  。 主要是为了适配 Spring Mvc 。

3.3 OAuth2ImportSelector

该类是为了对  OAuth2.0  开放授权协议进行支持。 ClientRegistration  如果被引用,具体点也就是  spring-security-oauth2  模块被启用(引入依赖jar)时。 会启用  OAuth2  客户端配置  OAuth2ClientConfiguration  。

3.4 @EnableGlobalAuthentication

这个类主要引入了  AuthenticationConfiguration  目的主要为了构造 认证管理器  AuthenticationManager  。 AuthenticationManager  十分重要后面我们会进行专门的分析。多多关注公众号:Felordcn 或者通过https://felord.cn 第一时间获取相关讲解。

4. SecurityFilterAutoConfiguration

我们在  org.springframework.boot.autoconfigure.security.servlet  路径下还发现了一个配置类  SecurityFilterAutoConfiguration  。 该类用于向Servlet容器注册一个名称为 securityFilterChainRegistration 的bean, 实现类是 DelegatingFilterProxyRegistrationBean 该 bean 的目的是注册另外一个  Servlet Filter Bean  到  Servlet  容器,实现类为  DelegatingFilterProxy  。 DelegatingFilterProxy  其实是一个代理过滤器,它被  Servlet  容器用于处理请求时,会将任务委托给指定给自己另外一个Filter bean。 对于  SecurityFilterAutoConfiguration ,来讲,这个被代理的Filter bean的名字为  springSecurityFilterChain  , 也就是我们上面提到过的 Spring Security Web提供的用于请求安全处理的Filter bean,其实现类是  FilterChainProxy

相关的源码分析:

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

  import java.util.EnumSet;
  import java.util.stream.Collectors;

  import javax.servlet.DispatcherType;

  import org.springframework.boot.autoconfigure.AutoConfigureAfter;
  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
  import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
  import org.springframework.boot.autoconfigure.security.SecurityProperties;
  import org.springframework.boot.context.properties.EnableConfigurationProperties;
  import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
  import org.springframework.security.config.http.SessionCreationPolicy;
  import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

  @Configuration
  // 仅在 Servlet 环境下生效
  @ConditionalOnWebApplication(type = Type.SERVLET)
  // 确保安全属性配置信息被加载并以bean形式被注册到容器
  @EnableConfigurationProperties(SecurityProperties.class)
  // 仅在特定类存在于 classpath 上时才生效
  @ConditionalOnClass({ AbstractSecurityWebApplicationInitializer.class,
          SessionCreationPolicy.class })
  // 指定该配置类在  SecurityAutoConfiguration 配置类应用之后应用
  @AutoConfigureAfter(SecurityAutoConfiguration.class)
  public class SecurityFilterAutoConfiguration {

      // 要注册到 Servlet 容器的 DelegatingFilterProxy Filter的
      // 目标代理Filter bean的名称 :springSecurityFilterChain
      private static final String DEFAULT_FILTER_NAME =
              AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;


      // 定义一个 bean securityFilterChainRegistration,
      // 该 bean 的目的是注册另外一个 bean 到 Servlet 容器 : 实现类为 DelegatingFilterProxy 的一个 Servlet Filter
      // 该 DelegatingFilterProxy Filter 其实是一个代理过滤器,它被 Servlet 容器用于匹配特定URL模式的请求,
      // 而它会将任务委托给指定给自己的名字为 springSecurityFilterChain 的 Filter, 也就是 Spring Security Web
      // 提供的用于请求安全处理的一个 Filter bean,其实现类是 FilterChainProxy
      // (可以将 1 个 FilterChainProxy 理解为 1 HttpFirewall + n SecurityFilterChain)
      @Bean
      @ConditionalOnBean(name = DEFAULT_FILTER_NAME)
      public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
              SecurityProperties securityProperties) {
          DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                  DEFAULT_FILTER_NAME);
          registration.setOrder(securityProperties.getFilter().getOrder());
          registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
          return registration;
      }

      private EnumSet<DispatcherType> getDispatcherTypes(
              SecurityProperties securityProperties) {
          if (securityProperties.getFilter().getDispatcherTypes() == null) {
              return null;
          }
          return securityProperties.getFilter().getDispatcherTypes().stream()
                  .map((type) -> DispatcherType.valueOf(type.name())).collect(Collectors
                          .collectingAndThen(Collectors.toSet(), EnumSet::copyOf));
      }

  }

5. 总结

本文主要对 Spring Security 在 Spring Boot 中的自动配置一些机制进行了粗略的讲解。 为什么没有细讲。 因为从学习出发有些东西不是我们必须要深入了解的,但是又要知道一点点相关的知识。 我们先宏观上有个大致的了解就行。 所以在阅读本文一定不要钻牛角尖。粗略知道配置策略、加载策略和一些关键类的作用即可。在你对 Spring Security 有了进一步学习之后,回头认真来看这些配置类会有更深层的思考。  从另一个方面该文也给你阅读 Spring 源码提供了一些思路,学会这些才是最重要的。

本文通过OpenWrite的免费Markdown转换工具发布

留言交流不过瘾

关注我,回复“ 加群 加入各种主题讨论群

Spring Security 实战:Spring Boot 下的自动配置

  • 9 个爱不释手的 JSON 工具

  • 手把手教你定制标准 Spring Boot starter

  • 程序员接私活的10个平台和一些建议

  • 经典面试题: Redis 内存满了怎么办?

  • 一个不错的权限管理模块设计案例

朕已阅 

原文  http://mp.weixin.qq.com/s?__biz=MzAxODcyNjEzNQ==&mid=2247488902&idx=3&sn=fc4140a574424772afddfd1e82122e96
正文到此结束
Loading...