SpringSecurity 初始化流程原始碼
SpringSecurity 初始化流程原始碼
本篇主要講解 SpringSecurity初始化流程的原始碼部分,包括核心的 springSecurityFilterChain 是如何建立的,以及在介紹哪裡可以擴充套件個性化的配置,SpringSecurity原始碼其實是蠻難得 各種Builder Configure 看得真的頭疼!
1.簡單介紹
SpringSecurity 的核心功能主要包括:
認證 (你是誰)
授權 (你能幹什麼)
攻擊防護 (防止偽造身份)
其核心就是一組過濾器鏈,專案啟動後將會自動配置,本篇也會涉及過濾器鏈是如何自動初始化的。
SecurityContextPersistenceFilter 是最前面的一個filter
請求到它時候會去檢查 根據sessionId找到session 判斷session 中是否存在 SecurityContext 在 則將 SecurityContext 存入當前的執行緒中去
響應的時候,看當前執行緒是否有SecurityContext ,如果有 放入到session中去 這樣不同的請求都能拿到相同的 使用者認證資訊。
UsernamePasswordAuthenticationFilter 該過濾器是處理表單登入的,通過表單登入提交的認證都會經過它處理
SocialAuthenticationFilter 比如這個就是社交登入使用的Filter
詳細可以看我另外一篇 SpringSocial 實現第三方QQ登入SpringSocial 實現第三方QQ登入
綠色的過濾器都是可配置的,其他顏色的都不行!
2.SecurityAutoConfiguration
如果是SpringBoot專案只要你依賴了SpringSecurity相關依賴依然會有自動配置類
SecurityAutoConfiguration 生效 它會匯入 WebSecurityEnableConfiguration
@EnableWebSecurity將會是我們本篇的主要切入點
3.@EnableWebSecurity註解介紹
該註解 它是初始化Spring Security的入口 .
開啟@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; }
該註解類通過@Configuration和@Import配合使用引入了一個配置類(WebSecurityConfiguration)和兩個ImportSelector(SpringWebMvcImportSelector,OAuth2ImportSelector),我們重點關注下WebSecurityConfiguration,它是Spring Security的核心
4.springSecurityFilterChain初始化流程及原始碼
開啟WebSecurityConfiguration 它是一個配置類,主要看 springSecurityFilterChain()方法,它就是初始化
springSecurityFilterChain的核心方法
/**
* Creates the Spring Security Filter Chain
* @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();
}
@Bean註解name屬性值AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME就是XML中定義的springSecurityFilterChain
從原始碼中知道過濾器通過最後的 webSecurity.build()建立,webSecurity的型別為:WebSecurity,它在 setFilterChainProxySecurityConfigurer方法中優先被建立了:
@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);
}
webSecurityConfigurers.sort(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;
}
從程式碼中可以看到,它是直接被new出來的:
webSecurity = objectPostProcessor
.postProcess(new WebSecurity(objectPostProcessor));
setFilterChainProxySecurityConfigurer 該方法的webSecurityConfigurers 引數是通過@Value注入的
@Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}")
AutowiredWebSecurityConfigurersIgnoreParents的 getWebSecurityConfigurers()
如下,就是獲取所有的 WebSecurityConfigurer的型別的配置類
而通常 我們通過繼承 WebSecurityConfigurerAdapter 來自定義WebSecurityConfigurer
public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() {
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<>();
Map<String, WebSecurityConfigurer> beansOfType = beanFactory
.getBeansOfType(WebSecurityConfigurer.class);
for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) {
webSecurityConfigurers.add(entry.getValue());
}
return webSecurityConfigurers;
}
再回到setFilterChainProxySecurityConfigurer方法 下面有一段這樣的程式碼 ,對於上面獲取的所有的WebSecurityConfigurer型別 迴圈執行 webSecurity的apply方法
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
webSecurity.apply(webSecurityConfigurer);
}
webSecurity整合AbstractConfiguredSecurityBuilder 它提供apply方法 再其內部呼叫add方法
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
add(configurer),主要就是將其傳入的WebSecurityConfigurer存入到 LinkedHashMap configures中,
主要程式碼 this.configurers.put(clazz, configs);
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
當所有的 WebSecurityConfigurer 型別的配置 全部應用到 WebSecurity中去後 setFilterChainProxySecurityConfigurer方法也就結束了
回到建立過濾器鏈的方法 springSecurityFilterChain()
它會判斷我們剛剛的webSecurityConfigurers是否存在,不存在就新建一個,然後執行 webSecurity.build() 重要!
@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();
}
最終內部會有下面這段程式碼, 主要關注 init() configure() 和 performBuild() 這三個方法
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
init() 內部迴圈遍歷 所有的 WebSecurityConfigurer ,它會執行到 WebSecurityConfigurerAdapter的
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
configurer.init((B) this)
它只要完成兩件重要的事情:
初始化HttpSecurity物件(注意它和WebSecurity不一樣 );
設定HttpSecurity物件新增至WebSecurity的securityFilterChainBuilders列表中;
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
初始化HttpSecurity物件在getHttp()方法中實現:
protected final HttpSecurity getHttp() throws Exception {
if (http != null) {
return http;
}
DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher);
AuthenticationManager authenticationManager = authenticationManager();
authenticationBuilder.parentAuthenticationManager(authenticationManager);
authenticationBuilder.authenticationEventPublisher(eventPublisher);
Map<Class<?>, Object> sharedObjects = createSharedObjects();
http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
sharedObjects);
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
// @formatter:on
ClassLoader classLoader = this.context.getClassLoader();
List<AbstractHttpConfigurer> defaultHttpConfigurers =
SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader);
for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
http.apply(configurer);
}
}
configure(http);
return http;
}
從程式碼中可以瞭解,HttpSecurity是直接被new出來的,在建立HttpSecurity之前,首先初始化了AuthenticationManagerBuilder物件,這裡有段程式碼很重要就是: AuthenticationManager authenticationManager = authenticationManager();,它建立AuthenticationManager例項,開啟authenticationManager()方法:
預設實現是在 WebSecurityConfigurerAdapter 中
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
1、個性化配置入口之configure(AuthenticationManagerBuilder auth)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置AuthenticationManagerBuilder。
如下是自己繼承WebSecurityConfigurerAdapter 重寫 configure(AuthenticationManagerBuilder auth),實現個性化的第一個配置入口
/**
* @author johnny
* @create 2020-01-18 下午6:40
**/
@Configuration
@Slf4j
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
log.info("【測試 定製化入口 configure(AuthenticationManagerBuilder auth) 的執行 】");
}
}
構建完HttpSecurity例項後,預設情況下會新增預設的攔截其配置:
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
.servletApi().and()
.apply(new DefaultLoginPageConfigurer<>()).and()
.logout();
我挑一個預設的方法展開看一下比如 會話管理的sessionManagement(),內部就是去建立SessionManagementConfigurer並應用它
public SessionManagementConfigurer<HttpSecurity> sessionManagement() throws Exception {
return getOrApply(new SessionManagementConfigurer<>());
}
getOrApply 最有一句程式碼 return apply(configurer);
private <C extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity>> C getOrApply(
C configurer) throws Exception {
C existingConfig = (C) getConfigurer(configurer.getClass());
if (existingConfig != null) {
return existingConfig;
}
return apply(configurer);
}
apply(configurer) 注意這裡的 configurer傳入的是SessionManagementConfigurer
public <C extends SecurityConfigurerAdapter<O, B>> C apply(C configurer)
throws Exception {
configurer.addObjectPostProcessor(objectPostProcessor);
configurer.setBuilder((B) this);
add(configurer);
return configurer;
}
最終又呼叫了 add(configurer); 這不過這裡是給 HttpSecurity的 configurers 配置初始的,上面是配置的WebSecurity的configurers, 不要混淆,最終這些configurers會被一個個建立成 對應的過濾器Filter的 詳細在後面有說明
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {
Assert.notNull(configurer, "configurer cannot be null");
Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer
.getClass();
synchronized (configurers) {
if (buildState.isConfigured()) {
throw new IllegalStateException("Cannot apply " + configurer
+ " to already built object");
}
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers
.get(clazz) : null;
if (configs == null) {
configs = new ArrayList<>(1);
}
configs.add(configurer);
this.configurers.put(clazz, configs);
if (buildState.isInitializing()) {
this.configurersAddedInInitializing.add(configurer);
}
}
}
如下圖:為HttpSecurity添加了很多預設的配置
回到 getHttp()方法
最後呼叫configure(http);,這又是一個可個性化的配置入口,它的預設實現是:WebSecurityConfigurerAdapter提供的
預設的配置是攔截所有的請求需要認證之後才能訪問,如果沒有認證,會自動生成一個認證表單要求輸入使用者名稱和密碼。
protected void configure(HttpSecurity http) throws Exception {
logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin().and()
.httpBasic();
}
2、個性化配置入口之configure(HttpSecurity http)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置HttpSecurity。
OK,目前為止HttpSecurity已經被初始化,接下去需要設定HttpSecurity物件新增至WebSecurity的securityFilterChainBuilders列表中:
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
當所有的WebSecurityConfigurer的init方法被呼叫之後,webSecurity.init()工作就結束了
接下去呼叫了webSecurity.configure(),該方法同樣是在AbstractConfiguredSecurityBuilder中實現的:
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
它的主要工作是迭代呼叫所有WebSecurityConfigurer的configurer方法,引數是WebSeucrity本身,這又是另外一個重要的個性化入口:
3、個性化配置入口之configure(WebSecurity web)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置WebSecurity。
至此,三個重要的個性化入口都已經被呼叫,即在實現WebSecurityConfigurerAdapter經常需要重寫的:
1、configure(AuthenticationManagerBuilder auth);
2、configure(WebSecurity web);
3、configure(HttpSecurity http);
回到webSecurity構建過程,接下去重要的的呼叫:
O result = performBuild();
performBuild() 非常重要!!
@Override
protected Filter performBuild() throws Exception {
Assert.state(
!securityFilterChainBuilders.isEmpty(),
() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "
+ "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. "
+ "More advanced users can invoke "
+ WebSecurity.class.getSimpleName()
+ ".addSecurityFilterChainBuilder directly");
int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size();
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(
chainSize);
for (RequestMatcher ignoredRequest : ignoredRequests) {
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
}
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) {
securityFilterChains.add(securityFilterChainBuilder.build());
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
if (httpFirewall != null) {
filterChainProxy.setFirewall(httpFirewall);
}
filterChainProxy.afterPropertiesSet();
Filter result = filterChainProxy;
if (debugEnabled) {
logger.warn("\n\n"
+ "********************************************************************\n"
+ "********** Security debugging is enabled. *************\n"
+ "********** This may include sensitive information. *************\n"
+ "********** Do not use in a production system! *************\n"
+ "********************************************************************\n\n");
result = new DebugFilter(filterChainProxy);
}
postBuildAction.run();
return result;
}
首先計算出chainSize,也就是ignoredRequests.size() + securityFilterChainBuilders.size();,如果你不配置ignoredRequests,那就是securityFilterChainBuilders.size(),也就是HttpSecurity的個數,其本質上就是你一共配置幾個WebSecurityConfigurerAdapter,因為每個WebSecurityConfigurerAdapter對應一個HttpSecurity,而所謂的ignoredRequests就是FilterChainProxy的請求,預設是沒有的,如果你需要條跳過某些請求不需要認證或授權,可以如下配置:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/statics/**");
}
在上面配置中,所有以/statics開頭請求都將被FilterChainProxy忽略。
securityFilterChains.add(securityFilterChainBuilder.build()); 這一行就是初始化所有的過濾器,記得上面有段程式碼如下,將HttpSecurity設定到WebSecurity的 securityFilterChainBuilder中,上面就是呼叫HttpSecurity.build()方法,初始化所有的 HttpSecurity的過濾器鏈
public void init(final WebSecurity web) throws Exception {
final HttpSecurity http = getHttp();
web.addSecurityFilterChainBuilder(http).postBuildAction(() -> {
FilterSecurityInterceptor securityInterceptor = http
.getSharedObject(FilterSecurityInterceptor.class);
web.securityInterceptor(securityInterceptor);
});
}
依然來到 doBuild()方法,只不過這次是執行的 HttpSecurity的
@Override
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init();
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild();
buildState = BuildState.BUILT;
return result;
}
}
重點檢視 configure()該方法 會呼叫對應的 過濾器配置的configure()
如 再內部建立 SessionManagementFilter 最後新增到HttpSecurity中,也就是拿 HttpSecurity的configures 一個個創建出對應的過濾器
@Override
public void configure(H http) {
SecurityContextRepository securityContextRepository = http
.getSharedObject(SecurityContextRepository.class);
SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(
securityContextRepository, getSessionAuthenticationStrategy(http));
if (this.sessionAuthenticationErrorUrl != null) {
sessionManagementFilter.setAuthenticationFailureHandler(
new SimpleUrlAuthenticationFailureHandler(
this.sessionAuthenticationErrorUrl));
}
InvalidSessionStrategy strategy = getInvalidSessionStrategy();
if (strategy != null) {
sessionManagementFilter.setInvalidSessionStrategy(strategy);
}
AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();
if (failureHandler != null) {
sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);
}
AuthenticationTrustResolver trustResolver = http
.getSharedObject(AuthenticationTrustResolver.class);
if (trustResolver != null) {
sessionManagementFilter.setTrustResolver(trustResolver);
}
sessionManagementFilter = postProcess(sessionManagementFilter);
http.addFilter(sessionManagementFilter);
if (isConcurrentSessionControlEnabled()) {
ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);
concurrentSessionFilter = postProcess(concurrentSessionFilter);
http.addFilter(concurrentSessionFilter);
}
}
當doBuild()中的 configure();執行完畢後 的會得到如下HttpSecurity可以看到它內部的filters已經全部建立完畢
回到doBuild()方法 該方中有 performBuild() 呼叫HttpSecurity的 performBuild(),預設實現如下,先對上面所有的過濾器進行排序,使用的是 FilterComparator() 進行排序的,這裡不展開了,反正就是會排序成文章開始的那張圖上面的順序
@Override
protected DefaultSecurityFilterChain performBuild() {
filters.sort(comparator);
return new DefaultSecurityFilterChain(requestMatcher, filters);
}
最後返回的是SecurityFilterChain的預設實現DefaultSecurityFilterChain。
構建完所有SecurityFilterChain後,建立最為重要的FilterChainProxy例項,
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
至此Spring Security 初始化完成,包括springSecurityFilterChain初始化,我們通過繼承WebSecurityConfigurerAdapter來代達到個性化配置目的,文中提到了三個重要的個性化入口,並且WebSecurityConfigurerAdapter是可以配置多個的,其對應的介面就是會存在多個SecurityFilterChain例項,但是它們人仍然在同一個FilterChainProxy中,通過RequestMatcher來匹配並傳入到對應的SecurityFilterChain中執行請求。
5.個性化入口配置(擴充套件WebSecurityConfigurerAdapter)
重要的個性化入口都是哪裡呼叫的 已經在上面初始化 springSecurityFilterChain 原始碼中講解了,這裡知識總結一下
1、個性化配置入口之configure(AuthenticationManagerBuilder auth)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置AuthenticationManagerBuilder。
2、個性化配置入口之configure(HttpSecurity http)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置HttpSecurity。
3、個性化配置入口之configure(WebSecurity web)
我們可以通過繼承WebSecurityConfigurerAdapter並重寫該方法來個性化配置WebSecurity。
實現WebSecurityConfigurerAdapter經常需要重寫的:
1、configure(AuthenticationManagerBuilder auth);
2、configure(WebSecurity web);
3、configure(HttpSecurity http);
6.總結
本篇主要講解了
1.SpringBoot對於SpringSecurity的自動配置的支援類SecurityAutoConfiguration,
2.核心註解@EnableWebSecurity
3. SpringSecurity的核心過濾器鏈 springSecurityFilterChain 的初始化流程的原始碼
原始碼部分還是定下心來多看 加油!
個人部落格地址: https://www.askajohnny.com 歡迎訪問!
本文由部落格一文多發平臺 OpenWrite 釋出!