SecurityAutoConfiguration源碼解析
SecurityAutoConfiguration 詳解
SpringBoot 對(duì) Security 的支持類(lèi)均位于
org.springframework.boot.autoconfigure.security包下,主要通過(guò) SecurityAutoConfiguration 自動(dòng)配置類(lèi)和 SecurityProperties 屬性配置來(lái)完成。
下面,我們通過(guò)對(duì) SecurityAutoConfiguration及引入的相關(guān)自動(dòng)配 置源碼進(jìn)行解析說(shuō)明。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (DefaultAuthenticationEventPublisher . class)
@EnableConfigurationProperties (SecurityProperties. class)
@Import({ SpringBootWebSecurityConfiguration. class, WebSecurityEnablerConfi
guration.
class,
SecurityDataConfiguration. class })public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(Authenticat ionEventPublisher. class)
public DefaultAuthenticat ionEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
@ConditionalOnClass指定classpath路徑中必須存在
DefaultAuthenticationEventPublisher 類(lèi) 才 會(huì) 進(jìn) 行 實(shí) 例 化 操 作 , 通 過(guò)@EnableConfigurationProperties 指定了配置文件對(duì)應(yīng)的類(lèi),通過(guò)@lmport 導(dǎo)入了SpringBootWebSecurityConfigurationWebSecurityEnablerConfiguration和Security-DataConfiguration 自動(dòng)配置類(lèi)。
首先,@
EnableConfigurationProperties 指定的 SecurityProperties 類(lèi),部分源碼如下。
@ConfigurationProperties(prefix = "spring . security" )
public class SecurityProperties {
private final Filter filter = new Filter();
private User user = new User();
public static class Filter {
// Security 過(guò)糖器鏈順序
private int order = DEFAULT_ FILTER_ _ORDER;
// Security 過(guò)濾器鏈分發(fā)類(lèi)型
private Set dispatcherTypes = new HashSet<>(
Arrays. asList(DispatcherType . ASYNC, DispatcherType . ERROR, Dispa-
tcherType . REQUEST));
public static class User {
//默認(rèn)用戶(hù)名
private String name = "user";
//默認(rèn)密碼
private String password = UUID. randomUUID() . toString();
// 默認(rèn)用戶(hù)角色
private List roles = new ArrayList<>();
}
}
} 通過(guò) SecurityProperties 中定義的配置項(xiàng),可以對(duì)照最開(kāi)始在 application.properties 文件中配置的用戶(hù)名和密碼,如果沒(méi)有進(jìn)行用戶(hù)名和密碼的配置,則默認(rèn)使用 user 作為用戶(hù)名,并自動(dòng)生成一個(gè) UUID 字符串作為密碼。那么,默認(rèn)密碼在哪里獲取呢?通常情況下,系統(tǒng)會(huì)在啟動(dòng)時(shí)的控制臺(tái)日志中打印出對(duì)應(yīng)的密碼信息,具體日志格式如下。
Using generated security password: 67bd059b-d503-4976-95b1-5fa09e6c9588而該日志的輸出功能是在
UserDetailsServiceAutoConfiguration 自動(dòng)配置類(lèi)中實(shí)例化InMemoryUserDetailsManager 類(lèi)時(shí)執(zhí)行的, 該類(lèi)是基于內(nèi)存的用戶(hù)詳情管理,比如提供用戶(hù)信息的增刪改查等操作。關(guān)于 UserDetailsServiceAutoConfiguration 自動(dòng)配置類(lèi),最核心的功能就是實(shí)例化了該類(lèi)的對(duì)象,我們不再過(guò)度展開(kāi),只看一下其中判斷和打印密碼的一個(gè)方法。
private String getOrDeducePassword(SecurityProperties .User user, PasswoFRiEn
String password = user . getPassword();
if (user. isPasswordGenerated()) {
logger . info(String . format("%n%nUsing generated security password: %s%
",user.
getPassword()));
if (encoder != null | PASSWORD_ ALGORITHM PATTERN. matcher(pas sword) . match
2s())
return password;
return NOOP_ PASSWORD_ PREFIX + password;
}
}在獲取密碼時(shí),通過(guò) SecurityProperties 中的 isPasswordGenerated 方法判斷是否是自動(dòng)生成的密碼,如果是,則通過(guò)日志打印出對(duì)應(yīng)的密碼。

下面繼續(xù)看 SecurityAutoConfiguration 導(dǎo)入的
SpringBootWebSecurityConfiguration 自動(dòng)配置類(lèi)。
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (WebSecurityConfigurerAdapter . class)
@ConditionalOnMiss ingBean(WebSecurityConfigurerAdapter .class)
@ConditionalOnWebApplication(type = Type . SERVLET)
public class SpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties . BASIC_ AUTH _ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
}
}該 自 動(dòng) 配 置 類(lèi) 為 Security 的 Web 應(yīng) 用 默 認(rèn) 配 置 , 當(dāng) 類(lèi) 路 徑 下 存 在
WebSecurityCon-figurerAdapter 類(lèi), 并且不存在對(duì)應(yīng)的 Bean 對(duì)象時(shí),會(huì)觸發(fā)該自動(dòng)配置類(lèi)。同時(shí),@ConditionalOnWebApplication 指定應(yīng)用類(lèi)型必須為 Servlet 應(yīng)用。該自動(dòng)配置類(lèi)的核心在于 WebSecurityConfigurerAdapter 適配器的實(shí)例化。用一句話來(lái)描述 SpringBootWebSecurityConfiguration 的功能就是:針對(duì)使用 Security 的 Web 應(yīng)用,如果用戶(hù)沒(méi)有注入自定義 WebSecurityConfigurerAdapter 的實(shí)現(xiàn)類(lèi), 則 Spring Boot 默認(rèn)提供一 個(gè)。
WebSecurityConfigurerAdapter 用于配置 Sping Security Web 安全。默認(rèn)情況下 SpringBoot 提供的 DefaultConfigurerAdapter 適配器實(shí)現(xiàn)為空,用 SecurityProperties 中常量BASIC_ _AUTH_ ORDER 指定的值(-5) 作為注入 Spring loC 容器的順序。
在正常使用的過(guò)程中,針對(duì) Web 項(xiàng)目我們都是通過(guò)繼承
WebSecurityConfigurer-Adapter,并實(shí)現(xiàn)其 configure(HttpSecurity http)方法來(lái)實(shí)現(xiàn)定制化設(shè)置的。下面看一下該類(lèi)的該方法的默認(rèn)實(shí)現(xiàn)。
@Order(100)
public abstract class WebSecurityConfigurerAdapter implements
WebSecurityConfigurer <WebSecurity> {
// @formatter:off
protected void configure(HttpSecurity http) throws Exception {
http. authorizeRequests()
. anyRequest() . authenticated()
. and()
. formLogin(). and()
.httpBasic();
}
}從上述默認(rèn)實(shí)現(xiàn)的代碼中可以看出,針對(duì)請(qǐng)求的攔截使用了 anyRequest 方法,該方法會(huì)匹配所有的請(qǐng)求路徑。同時(shí),Security 還提供 了基于 Ant 風(fēng)格的路徑匹配方法(antMatches)和基于正則表達(dá)式的匹配方法(regexMathes)。
另外通過(guò) formLogin 方法,設(shè)置了默認(rèn)登錄時(shí)的登錄請(qǐng)求、用戶(hù)名、密碼等信息,在其調(diào)用過(guò)程中會(huì)創(chuàng)建一-個(gè) FormLoginConfigurer 對(duì)象,用來(lái)設(shè)置默認(rèn)信息。FormL oginConfigurer構(gòu)造方法如下。
public FormL oginConfigurer() {
super(new UsernamePas swordAuthenticationFilter(), null);
usernameParameter("username");
passwordParameter("password");
}其中
UsernamePasswordAuthenticationFilter 中定義了請(qǐng)求跳轉(zhuǎn)的頁(yè)面。
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));}這就是當(dāng)引入 Security 框架之后,訪問(wèn)頁(yè)面時(shí)會(huì)默認(rèn)跳轉(zhuǎn)到 login 頁(yè)面的原因了。
下 面 繼 續(xù) 看SecurityAutoConfiguration引 入 的 自 動(dòng) 配 置 類(lèi)
WebSecurityEnablerConfigu-ration。在早期版本中,當(dāng)我們使用 Security 時(shí)還需要自己使用 @EnableWebSecurity 注 解 , Spring Boot2.0.0 版 本 新 增 的WebSecurityEnablerConfiguration 幫我們解決了該問(wèn)題,該類(lèi)源碼如下。
@Configuration(proxyBeanMethods = false)
@ConditionalOnBean(WebSecurityConfigurerAdapter. class)
@ConditionalOnMi ssingBean(name = BeanIds . SPRING_ SECURITY_ FILTER_
_CHAIN)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication. Type . SERVLE
T)
@EnableWebSecurity
public class WebSecurityEnablerConfiguration {
}
該類(lèi)并沒(méi)有具體的實(shí)現(xiàn),重點(diǎn)在于滿(mǎn)足條件時(shí)激活@EnableWebSecurity 注解,即當(dāng)WebSecurityConfigurerAdapter 對(duì)應(yīng)的 Bean 存在,name 為 springSecurityFilterChain 的Bean 不存在,應(yīng)用類(lèi)型為 Servlet 時(shí),激活@EnableWebSecurity 注解。該自動(dòng)配置類(lèi)的主要作用是防止用戶(hù)漏使用@EnableWebSecurity 注解,通過(guò)該自動(dòng)配置類(lèi)確保@EnableWebSecurity 注解被使用,從而保障 springSecurityFilterChain Bean 的定義。
我們看一下 @EnableWebSecurity 的源碼。
@Retention(value = java. lang . annotation. Retent ionPolicy . RUNTIME)
@Target(value = { java. lang . annotation. ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration. class,
SpringWebMvc ImportSe lector.class,
OAuth2ImportSelector .class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
boolean debug() default false;
}@EnableWebSecurity 用來(lái)控制 Spring Security 是否使用調(diào)試模式,并且組合了其他自動(dòng)配置。導(dǎo)入了 WebSecurityConfiguration,用于配置 Web 安全過(guò)濾器 FilterChainProxy。

如果是 Servlet 環(huán)境,導(dǎo)入
WebMvcSecurityConfiguration;如果是 OAuth2 環(huán)境,導(dǎo)入OAuth2ClientConfiguration。使用注解@EnableGlobalAuthentication 啟用全局認(rèn)證機(jī)制。最后,我們看一下 SecurityAutoConfiguration 引入的 SecurityDataConfiguration.
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass (SecurityEvaluat ionContextExtension. class)
public class SecurityDataConfiguration {
@Bean
@Conditiona lOnMissingBean
public SecurityEvaluationContextExtension securityEvaluationContextExtens
ion() {
return new SecurityEvaluat ionContextExtension();
}
}在該自動(dòng)配置類(lèi)中實(shí)例化了
SecurityEvaluationContextExtension 類(lèi)的對(duì)象,其主要作用是將 Spring Security 與 Spring Data 進(jìn)行整合。
回 到SecurityAutoConfiguration類(lèi) 內(nèi) 部 , 它 實(shí) 例 化 了 一 個(gè)
DefaultAuthenticationEvent-Publisher 將其作為默認(rèn)的 AuthenticationEventPublisher, 并將其注入 Spring 容器。
DefaultAuthenticationEventPublisher 為發(fā)布身份驗(yàn)證事件的默認(rèn)策略類(lèi),將眾所周知的AuthenticationException 類(lèi)型映射到事件中, 并通過(guò)應(yīng)用程序上下文進(jìn)行發(fā)布。如果配置為 Bean,它將自動(dòng)獲取 ApplicationEventPublisher。否則,應(yīng)該使用構(gòu)造方法將 ApplicationEventPublisher 傳入。
DefaultAuthenticationEventPublisher 內(nèi)部通過(guò) HashMap 維護(hù)認(rèn)證異常處理和對(duì)應(yīng)異常事件處理邏輯的映射關(guān)系,發(fā)生不同認(rèn)證異常會(huì)采用不同的處理策略。我們看一下該類(lèi)的部分源碼。
public class DefaultAuthenticationEventPublisher implements AuthenticationE
ventPublisher,
ApplicationEventPublisherAware {
private Applicat ionEventPublisher applicationEventPublisher;
private final HashMapnEvent>>
except ionMappings = new HashMaphentica-
tionEvent>>();
public DefaultAuthenticat ionEventPublisher(
ApplicationEventPublisher applicationEventPublisher) {
this . applicationEventPublisher = applicationEventPublisher;addMapping(BadCredentialsException. class . getName() ,
AuthenticationFailureBadCredentialsEvent. class);
addMapping(UsernameNotFoundException. class . getName() ,
AuthenticationF ailureBadCredent ialsEvent . class);
addMapping(AccountExpiredException. class . getName( )
AuthenticationF ailureExpiredEvent.class);
addMapping(ProviderNotFoundExcept ion. class . getName(),
Authenticat ionFai lureProviderNotFoundEvent. class);
addMapping(DisabledException. class . getName(),
Authenticat ionF ailureDisabledEvent . class);
addMapping(LockedException. class . getName(),
AuthenticationFailureLockedEvent . class);
addMapping(Authenticat ionServiceException. class . getName(),
AuthenticationFailureServiceExceptionEvent. class);
addMapping(CredentialsExpiredException. class . getName(),
AuthenticationF ailureCredentialsExpiredEvent. class);
addMapping(
"org. springframework . security . authentication. cas . ProxyUntrustedExcept
ion",
AuthenticationFailureProxyUntrustedEvent. class);
}
} 在上述代碼中提供了未找到用戶(hù)異常( UsernameNotFoundException )、賬戶(hù)過(guò)期異常(AccountExpiredException) 等 常 見(jiàn) 異 常 的 對(duì) 應(yīng) 事 件 。同 時(shí) , 該 類(lèi) 集 成 了 Spring 的
Application-EventPublisher, 通 過(guò) ApplicationEventPublisher 可 以 將 定 義 在exceptionMappings 中的異常事件進(jìn)行發(fā)布,相關(guān)核心代碼如下。
public void publishAuthenticat ionFailure(Authenticat ionException exceptm,
Authentication authentication) {
//根據(jù)異常名稱(chēng)獲得對(duì)應(yīng)事件的構(gòu)造器
Constructor extends AbstractAuthenticationEvent> constructor = exceptio
nMappings
. get(exception. getClass(). getName());
AbstractAuthenticationEvent event = null;
// 如果構(gòu)造器不為 null, 則實(shí)例化對(duì)應(yīng)的對(duì)象
if (constructor != null) {
tryevent = constructor . newInstance(authentication, exception);
} catch (IllegalAccessException| InvocationTargetException| Instantia
tion
Exception ignored) {
//如果對(duì)象實(shí)例化成功,則調(diào)用 Appl icationEventPubl isher 的 publ ishEvent 方法進(jìn)
行發(fā)布
if (event != null) {
if (applicationEventPublisher != null) {
applicationEventPublisher . publishEvent (event);
} else {
if (logger. isDebugEnabled())
logger. debug("No event was found for the exception
+ exception. getClass().getName());
}
}
}上述代碼的操作就是根據(jù)異常信息在 exceptionMappings 中獲得對(duì)應(yīng)事件的構(gòu)造方法,然后實(shí)例化對(duì)象,并調(diào)用 ApplicationEventPublisher 的 publishEvent 方法進(jìn)行發(fā)布。
至此,關(guān)于 SecurityAutoConfiguration 的自動(dòng)配置過(guò)程已經(jīng)完成了。
SecurityFilterAutoConfiguration 詳解
SecurityFilterAutoConfiguration 主要用于自動(dòng)配置 Spring Security 的 Filter,該自動(dòng)配置類(lèi)與 SpringBootWebSecurityConfiguration 分 開(kāi) 配 置 , 以 確 保 在 存 在 用 戶(hù) 提 供 的WebSecurity-Configuration 情況下仍可以配置過(guò)濾器的順序。
下面看一下
SecurityFilterAutoConfiguration 類(lèi)的源代碼。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type . SERVLET)
@EnableConfigurationProperties (SecurityProperties.class)
@ConditionalOnClass({ AbstractSecurityWebApplicationInitializer .class, Sess
ionCreationPolicy. class })
@AutoConfigureAfter(SecurityAutoConfiguration. class)
public class SecurityFilterAutoConfiguration {
private static final String DEFAULT FILTER NAME = AbstractSecurityWebApp-
licationInitializer .DEFAULT_ FILTER_ NAME ;
@Bean@ConditionalOnBean(name = DEFAULT_ FILTER_ NAME)
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistrat
ion(
SecurityProperties securityProperties) {
DelegatingF ilterProxyRegistrat ionBean registration = new DelegatingFi-
lterProxyRegistrationBean(
DEFAULT_ FILTER_ NAME);
registrat ion. setOrder( securityProperties . getFilter(). get0rder());
registration. setDispatcherTypes(getDispatcherTypes(securityPropertie
s));
return registration;
private EnumSet getDispatcherTypes (SecurityProperties sec
urityProperties) {
if (securityProperties . getFilter(). getDispatcherTypes() == null) {
return null;
return securityProperties . getFilter(). getDispatcherTypes(). stream()
.map((type) -> DispatcherType . value0f(type .name()))
. collect(Collectors . collectingAndThen(Collectors . toSet(), EnumSet: : copy
0f));
}
} 通 過(guò) 注 解 部 分 可 以 看 出 : 當(dāng) 項(xiàng) 目 為 Web 的 Servlet 項(xiàng) 目 , 類(lèi) 路 徑 下 存 在 類(lèi)SessionCreation-Policy 和
AbstractSecurityWebApplicationlnitializer 時(shí) ,會(huì) 在SecurityAutoConfiguration 配置完成之后進(jìn)行自動(dòng)配置,并導(dǎo)入 SecurityProperties 配置類(lèi)。
在
SecurityFilterAutoConfiguration 的內(nèi)部實(shí)現(xiàn)中,主要向容器中注冊(cè)了一個(gè)名稱(chēng)為securityilterChainRegistration的Bean,具體實(shí)現(xiàn)類(lèi)是DelegatingFilterProxyRegistrationBean.
常 量 DEFAULT_FILTER_NAME 定 義 了 要 注 冊(cè) 到 Servlet 容 器 的
DelegatingFilterProxy-Filter 的目標(biāo)代理 Filter Bean ,名稱(chēng)為 springSecurityFilterChain。
securityFilterChainRegistration 方法用@ConditionalOnBean 注解判斷容器中是否存在名稱(chēng)為 springSecurityFilterChain 的 Bean, 如果不存在,則執(zhí)行該方法內(nèi)的操作。
在
securityFilterChainRegistration 方法 內(nèi) , 首 先 創(chuàng) 建 了 一 個(gè)DelegatingilterProxyRegist-rationBean 對(duì)象,并以 springSecuritFilterChain 參數(shù)作為委托的目標(biāo)類(lèi)的名稱(chēng),也就是要在 Spring 應(yīng)用程序上下文中查找的目標(biāo)過(guò)濾器的 Bean 的名稱(chēng)。
DelegatingFilterProxyRegistrationBean 本質(zhì)上是一個(gè) ServletContextlnitializer,用于在Servlet 3.0+ 容器中注冊(cè) DelegatingFilterProxys.與 ServletContext 提供的注冊(cè)功能相似,但 DelegatigFilterProxyRegistrationBean 具有 Spring Bean 的友好性設(shè)計(jì)。通常,應(yīng)該使用構(gòu)造方法的 targetBeanName 參數(shù)指定實(shí)際委托過(guò)濾器的 Bean 名稱(chēng)(上述源代碼便是 如此操作)。與 FilterRegistrationBean 不同, 引用的過(guò)濾器不會(huì)過(guò)早的被實(shí)例化。實(shí)際上,如果將委托過(guò)濾器 Bean 標(biāo)記為@Lazy,則在調(diào)用過(guò)濾器之前根本不會(huì)實(shí)例化它。
在
DelegatingFilterProxyRegistrationBean 內(nèi)部,實(shí)現(xiàn)了通過(guò)傳入的 targetBeanName 名字,在 WebApplicationContext 查找該 Fillter 的 Bean, 并通過(guò) DelegatingFilterProxy 生成基于該 Bean 的代理 Filter 對(duì)象。
DelegatingFilterProxy 其實(shí)是-個(gè)代理過(guò)濾器,Servlet 容器處理請(qǐng)求時(shí), 會(huì)將任務(wù)委托給指定給的 Filter Bean。在該自動(dòng)配置類(lèi)中就是名稱(chēng)為 springSecurityFilterChain 的 Bean,該Bean 也是 Spring Security Web 提供的用于請(qǐng)求安全處理的 Filter Bean。
實(shí) 例 化
DelegatingFilterProxyRegistrationBean 之 后 , 便 對(duì) 其 設(shè) 置 優(yōu) 先 級(jí) , 默 認(rèn) 為SecurityProperties 中定義的 DEFAULT_ _FILTER_ ORDER 的值(-100)。最后,設(shè)置其DispatcherTypes。SecurityFilterAutoConfiguration 中的 getDispatcherTypes 方法便是根據(jù)配置獲得對(duì)應(yīng)的調(diào)度類(lèi)型的集合。在 Servlet 中,調(diào)度類(lèi)型定義在枚舉類(lèi) DispatcherType中,包括: FORWARD、INCLUDE、REQUEST、ASYNC 和 ERROR 這 5 種類(lèi)型。至此,關(guān)于 SecurityFilterAutoConfiguration 的自動(dòng)化配置及功能講解完畢。

小結(jié)
本章重點(diǎn)進(jìn)行了在 Web Servlet 下 Spring Security 的自動(dòng)配置源碼解析。
Spring Boot支持很 多 Spring Security的 自動(dòng)配 置 , 均 位 于
org.springframework.boot.autoconfigure.security 包下,限于篇幅無(wú)法一一講解,大家可根據(jù)需要自行閱讀。而關(guān)于 Spring Security 更多功能的具體使用,我們可參考官方文檔和相關(guān)書(shū)籍進(jìn)行學(xué)習(xí)實(shí)踐。
本文給大家講解的內(nèi)容是SpringBootSecurity支持:SecurityAutoConfiguration 詳解
下篇文章給大家講解的是微服務(wù)架構(gòu)與Spring Cloud;
覺(jué)得文章不錯(cuò)的朋友可以轉(zhuǎn)發(fā)此文關(guān)注小編;
感謝大家的支持!
本文就是愿天堂沒(méi)有BUG給大家分享的內(nèi)容,大家有收獲的話可以分享下,想學(xué)習(xí)更多的話可以到微信公眾號(hào)里找我,我等你哦。
