文章目錄
- 1. 前言
- 2. Spring Boot 下 Spring Security 的自動配置
- 2.1 SecurityAutoConfiguration
- 2.2 SpringBootWebSecurityConfiguration
- 2.3 WebSecurityEnablerConfiguration
- 3. @EnableWebSecurity 注解
- 3.1 WebSecurityConfiguration
- 3.2 SpringWebMvcImportSelector
- 3.3 OAuth2ImportSelector
- 3.4 @EnableGlobalAuthentication
- 4. SecurityFilterAutoConfiguration
- 5. 總結
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 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
&& !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
客戶端配置 OAuth2ClientConfiguration
。
3.4 @EnableGlobalAuthentication
這個類主要引入了 AuthenticationConfiguration
目的主要為了構造 認證管理器 AuthenticationManager
。AuthenticationManager
十分重要后面我們會進行專門的分析。
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 源碼提供了一些思路,學會這些才是最重要的。
關注公眾號:Felordcn獲取更多資訊