咱們在前幾篇對 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 Security 在 Spring Boot 的配置和使用。java
咱們能夠經過 org.springframework.boot.autoconfigure.security.servlet
路徑下找到 Spring Security 關於Servlet
的自動配置類。咱們來大體瞭解一下。web
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
,也就是說發生不一樣認證的異常使用不一樣處理策略。spring
@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
@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
註解。那麼這個註解都有什麼呢?安全
@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
註解。app
該配置類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; } }
該類是爲了對 Spring Mvc 進行支持的。一旦發現應用使用 Spring Mvc 的核心前置控制器 DispatcherServlet
就會引入 WebMvcSecurityConfiguration
。主要是爲了適配 Spring Mvc 。源碼分析
該類是爲了對 OAuth2.0
開放受權協議進行支持。ClientRegistration
若是被引用,具體點也就是 spring-security-oauth2
模塊被啓用(引入依賴jar)時。會啓用 OAuth2
客戶端配置 OAuth2ClientConfiguration
。post
這個類主要引入了 AuthenticationConfiguration
目的主要爲了構造 認證管理器 AuthenticationManager
。AuthenticationManager
十分重要後面咱們會進行專門的分析。
咱們在 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)); } }
本文主要對 Spring Security 在 Spring Boot 中的自動配置一些機制進行了粗略的講解。爲何沒有細講。由於從學習出發有些東西不是咱們必需要深刻了解的,可是又要知道一點點相關的知識。咱們先宏觀上有個大體的瞭解就行。因此在閱讀本文必定不要鑽牛角尖。粗略知道配置策略、加載策略和一些關鍵類的做用便可。在你對 Spring Security 有了進一步學習以後,回頭認真來看這些配置類會有更深層的思考。 從另外一個方面該文也給你閱讀 Spring 源碼提供了一些思路,學會這些纔是最重要的。
關注公衆號:Felordcn獲取更多資訊