Spring Security 實戰乾貨:Spring Boot 中的 Spring Security 自動配置初探

1. 前言

咱們在前幾篇對 Spring Security 的用戶信息管理機制,密碼機制進行了探討。咱們發現 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 SecuritySpring Boot 的配置和使用。java

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

咱們能夠經過 org.springframework.boot.autoconfigure.security.servlet 路徑下找到 Spring Security 關於Servlet的自動配置類。咱們來大體瞭解一下。web

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)了 SpringBootWebSecurityConfigurationWebSecurityEnablerConfigurationSecurityDataConfiguration 三個配置類。 讓這三個模塊的類生效。是一個複合配置,是 Spring Security 自動配置最重要的一個類之一。 Spring Boot 自動配置常常使用這種方式以達到靈活配置的目的,這也是咱們研究 Spring Security 自動配置的一個重要入口 同時 SecurityAutoConfiguration 還將 DefaultAuthenticationEventPublisher 做爲默認的 AuthenticationEventPublisher 注入 Spring IoC 容器。若是你熟悉 Spring 中的事件機制你就會知道該類是一個 Spring 事件發佈器。該類內置了一個HashMap<String, Constructor<? extends AbstractAuthenticationEvent>>維護了認證異常處理和對應異常事件處理邏輯的映射關係,好比帳戶過時異常 AccountExpiredException 對應認證過時事件AuthenticationFailureExpiredEvent ,也就是說發生不一樣認證的異常使用不一樣處理策略。spring

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 來實現。咱們會在之後的博文重點介紹該類。express

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 導入了 WebSecurityConfigurationSpringWebMvcImportSelectorOAuth2ImportSelector 以及啓用了 @EnableGlobalAuthentication註解。app

3.1 WebSecurityConfiguration

該配置類WebSecurityConfiguration使用一個WebSecurity對象基於用戶指定的或者默認的安全配置,你能夠經過繼承 WebSecurityConfigurerAdapter 或者實現 WebSecurityConfigurer 來定製 WebSecurity 建立一個FilterChainProxy Bean來對用戶請求進行安全過濾。這個FilterChainProxy的名稱就是 WebSecurityEnablerConfiguration上的 BeanIds.SPRING_SECURITY_FILTER_CHAIN 也就是 springSecurityFilterChain,它是一個Filter,最終會被做爲Servlet過濾器鏈中的一個Filter應用到Servlet容器中。安全處理的策略主要是過濾器的調用順序。WebSecurityConfiguration 最終會經過 @EnableWebSecurity 應用到系統。ide

源碼分析:spring-boot

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
                 && !webSecurityConfigurers.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 客戶端配置 OAuth2ClientConfigurationpost

3.4 @EnableGlobalAuthentication

這個類主要引入了 AuthenticationConfiguration 目的主要爲了構造 認證管理器 AuthenticationManagerAuthenticationManager 十分重要後面咱們會進行專門的分析。

4. SecurityFilterAutoConfiguration

咱們在 org.springframework.boot.autoconfigure.security.servlet 路徑下還發現了一個配置類 SecurityFilterAutoConfiguration 。該類用於向Servlet容器註冊一個名稱爲securityFilterChainRegistration的bean, 實現類是DelegatingFilterProxyRegistrationBean。該 bean 的目的是註冊另一個 Servlet Filter BeanServlet 容器,實現類爲 DelegatingFilterProxyDelegatingFilterProxy 實際上是一個代理過濾器,它被 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 源碼提供了一些思路,學會這些纔是最重要的。

關注公衆號:Felordcn獲取更多資訊

我的博客:https://felord.cn

相關文章
相關標籤/搜索