網上已有一些對於SpringSecurity的分析解讀,寫的很好,看過以後受益良多!java
好比:web
https://www.cnkirito.moe/categories/Spring-Security/spring
http://www.spring4all.com/article/428json
有興趣能夠先研讀下數組
這裏的博客主要是參考他人文章,順便記錄下本身的理解,僅此安全
之前學習過Spring Security的XML配置形式,如今基本都使用Java Config形式了,這點的變化仍是蠻大的session
XML形式:mvc
<filter> <filter-name>springSecurityFilterChain</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
這裏主要是註冊一個過濾器DelegatingFilterProxy類,映射的name爲springSecurityFilterChain,直觀。app
暫不分析async
Java Config形式:
@Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { //... }
能夠看到,聲明一個配置類,主要特色有:
1:@EnableWebSecurity註解
2:繼承WebSecurityConfigurerAdapter類
3:重寫config方法
4:HttpSecurity類的使用
因爲被@Configuration修飾,不出意外,應該會在Spring啓動的時候加載。看下@EnableWebSecurity註解的定義:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class }) @EnableGlobalAuthentication @Configuration public @interface EnableWebSecurity { }
是一個多重註解,加載了WebSecurityConfiguration.class,SpringWebMvcImportSelector.class和@EnableGlobalAuthentication註解,繼續跟進後者:
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) @Target(value = { java.lang.annotation.ElementType.TYPE }) @Documented @Import(AuthenticationConfiguration.class) @Configuration public @interface EnableGlobalAuthentication { }
發現其又加載AuthenticationConfiguration.class
綜上可知,主要是加載了三個類:
<1>SpringWebMvcImportSelector的做用是判斷當前的環境是否包含springmvc,由於spring security能夠在非spring環境下使用,爲了不DispatcherServlet的重複配置,因此使用了這個註解來區分。
<2> WebSecurityConfiguration顧名思義,是用來配置web安全的,下面的小節會詳細介紹。
<3>AuthenticationConfiguration權限配置相關類
而重點就是後二者!
下面啓動Spring,跟蹤下源碼,查看它如何加載的:
上述是啓動對應的時序圖。
由上所述,咱們主要關注三個起點類:
註解引入:WebSecurityConfiguration,AuthenticationConfiguration
自定義類的父類:WebSecurityConfigurerAdapter
自定義啓動類以下:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.password.NoOpPasswordEncoder; @Configuration @EnableWebSecurity public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter { MyAuthenticationFailureHandler failHander = new MyAuthenticationFailureHandler(); @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // .antMatchers("/") // .permitAll() // 請求路徑"/"容許訪問 // .anyRequest() // .authenticated() // 其它請求都不須要校驗才能訪問 .antMatchers("/home") .hasRole("LOGOPR") .and() .formLogin() .loginPage("/login") // 定義登陸的頁面"/login",容許訪問 .permitAll() .failureUrl("/login?#error=1111") // .failureHandler(failHander) .and() .logout() // 默認的"/logout", 容許訪問 .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { System.out.println("--------------AuthenticationManagerBuilder-----------"); // 在內存中注入一個用戶名爲anyCode密碼爲password而且身份爲USER的對象 auth.inMemoryAuthentication().withUser("username").password("password").roles("USER"); } /* * 問題描述:在寫基於Spring cloud微服務的OAuth2認證服務時,由於Spring-Security從4+升級到5+, * 致使There is no PasswordEncoder mapped for the id 「null」錯誤。 * */ @Bean public static NoOpPasswordEncoder passwordEncoder() { return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance(); } }
debug啓動應用,進入AuthenticationConfiguration,生成了一個bean:AuthenticationManagerBuilder,以下:
是一個初始化的狀態,裏面的Providers爲空,parentAuthenticationManager爲空,狀態爲UNBUILT
接着經過setFilterChainProxySecurityConfigurer注入bean
@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; }
其中包括咱們自定義的MyWebSecurityConfig代理類:
能夠看到是一個List類型,也說明能夠同時自定義多個Config類
接着就new出一個WebSecurity對象,遍歷上述List,將自定義MyWebSecurityConfig應用於webSecurity:webSecurity.apply(webSecurityConfigurer)
這裏調用的是AbstractConfiguredSecurityBuilder
List<SecurityConfigurer<O, B>> configs = allowConfigurersOfSameType ? this.configurers .get(clazz) : null; if (configs == null) { configs = new ArrayList<SecurityConfigurer<O, B>>(1); } configs.add(configurer); this.configurers.put(clazz, configs);
主要是爲了初始化:
LinkedHashMap<Class,List<SecurityConfigurer>> configurers,
它存儲的是自定義的MyWebSecurityConfig,key是類對應的Class,value是此List<SecurityConfigurer<O, B>>,後續還會用到configurers這個全局變量,這裏執行完畢,此初始化方法執行完畢!
繼續跟進,直到執行@Bean # springSecurityFilterChain(),產一個過濾器鏈,這是較爲核心的部分!
代碼以下:
@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(); }
直接執行到最後一句,經過webSecurity.build()建立過濾器鏈,跟進,調用父類AbstractSecurityBuilder#build(),根據時序圖,後面會屢次調用此類的build()方法,比較複雜。。。
public final O build() throws Exception { if (this.building.compareAndSet(false, true)) { this.object = doBuild(); return this.object; } throw new AlreadyBuiltException("This object has already been built"); }
繼續調用子類的AbstractConfiguredSecurityBuilder#doBuild()方法:
@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; } }
看下繼承類圖:
這四個類穿插不停的執行。。。
繼續執行AbstractConfiguredSecurityBuilder#init()方法,這裏調用的是它自身的init方法
@SuppressWarnings("unchecked") 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); } }
private Collection<SecurityConfigurer<O, B>> getConfigurers() { List<SecurityConfigurer<O, B>> result = new ArrayList<SecurityConfigurer<O, B>>(); for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) { result.addAll(configs); } return result; }
由前所述及時序圖可知,這裏的this.configurers已在前面初始化過,value存儲的就是自定義的MyWebSecurityConfig,因此這裏遍歷configurers,實際調用的是MyWebSecurityConfig的init類,因爲沒有重寫init類,因此這裏就調用了父類WebSecurityConfigurerAdapter的init()方法:
public void init(final WebSecurity web) throws Exception { final HttpSecurity http = getHttp(); web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); }
接着就調用了getHttp()
@SuppressWarnings({ "rawtypes", "unchecked" }) protected final HttpSecurity getHttp() throws Exception { if (http != null) { return http; } DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); //獲取AuthenticationManager對象,這個類在登錄的時候會用上,用來產生登錄結果:Authentication AuthenticationManager authenticationManager = authenticationManager(); authenticationBuilder.parentAuthenticationManager(authenticationManager); Map<Class<? extends Object>, 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); } } //調用子類方法,基本就是自定義的Config類 configure(http); return http; }
這個方法基本就是獲取各類配置了:
首先是獲取AuthenticationManager對象,這個類能夠用來生成Authentication對象,也就是登錄最終生成的結果
protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) { configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { authenticationManager = authenticationConfiguration .getAuthenticationManager(); } else { authenticationManager = localConfigureAuthenticationBldr.build(); } authenticationManagerInitialized = true; } return authenticationManager; }
這裏是經過authenticationConfiguration.getAuthenticationManager(),這裏的authenticationConfiguration是一個全局AuthenticationConfiguration對象,由Spring生成的代理對象:
它的屬性AuthenticationManager爲null,且未被初始化,調用其getAuthenticationManager()
public AuthenticationManager getAuthenticationManager() throws Exception { if (this.authenticationManagerInitialized) { return this.authenticationManager; } AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder( this.objectPostProcessor, this.applicationContext); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); } for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { authBuilder.apply(config); } authenticationManager = authBuilder.build(); if (authenticationManager == null) { authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return authenticationManager; }
這裏調用authBuilder.apply(config),實際是繼續初始化AbstractConfiguredSecurityBuilder的全局變量:
LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers
該對象在上面已經初始化過了,後面調用authBuilder.build(),它繼續回調AbstractSecurityBuilder,操做的仍然是上述的configurers,比較複雜,略過
其中,在此build()方法中當調用performBuild()時:
@Override protected ProviderManager performBuild() throws Exception { if (!isConfigured()) { logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); return null; } ProviderManager providerManager = new ProviderManager(authenticationProviders, parentAuthenticationManager); if (eraseCredentials != null) { providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials); } if (eventPublisher != null) { providerManager.setAuthenticationEventPublisher(eventPublisher); } providerManager = postProcess(providerManager); return providerManager; }
這裏就是產生ProviderManager
這裏默認的Provider就是DaoAuthenticationProvider,這裏未看到如何注入,應該也是以前初始化的吧,建立完畢調用providerManager = postProcess(providerManager),這裏調用的是spring的AutowireCapableBeanFactory,目的是將ProviderManager裝配成一個bean,最終返回此對象!
一步步返回,回到了authBuilder.build(),獲得了AuthenticationManager的實例對象:ProviderManager,這個類在表單登錄是是一個很重要的核心類!
在比對下對象authenticationConfiguration
能夠看到屬性已經不爲空,初始化狀態變爲true了
繼續返回到getHttp()方法,進行默認的HttpSecurity配置,略過,直到執行configure(http)方法,這裏實際調用的就是自定義的MyWebSecurityConfig的configure方法:
執行前,看下http對象:
屬性以下:
buildState = UNBUILT configures = { class org.springframework.security.config.annotation.web.configurers.CsrfConfigurer=[org.springframework.security.config.annotation.web.configurers.CsrfConfigurer@2dd8239], class org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer=[org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer@472698d], class org.springframework.security.config.annotation.web.configurers.HeadersConfigurer=[org.springframework.security.config.annotation.web.configurers.HeadersConfigurer@7b7683d4], class org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer=[org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer@40712ee9], class org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer=[org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer@2e53b094], class org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer=[org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer@39fa8ad2], class org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer=[org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer@76ddd61a], class org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer=[org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer@3f92a84e], class org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer=[org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer@cf67838], class org.springframework.security.config.annotation.web.configurers.LogoutConfigurer=[org.springframework.security.config.annotation.web.configurers.LogoutConfigurer@6137cf6e] } filter = [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@56f521c6] RequestMatcher = org.springframework.security.web.util.matcher.AnyRequestMatcher@1
執行完畢以後返回到AbstractSecurityBuilder,init()方法執行完畢,繼續執行performBuild(),調用WebSecurity的perfotmBuild():
@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; }
這裏首先根據securityFilterChainBuilders的大小建立一個數組,通常都只有一個過濾器鏈,因此size=1,遍歷此對象
就是HttpSecurity對象,上面羅列出的他的configurers屬性,包含的是各類Configurer對象,這些對象就是用來生成Filter對象!
遍歷調用configurer.build(),仍然是調用AbstractSecurityBuilder的build(),調用太屢次了,不深刻了,最終生成了一個securityFilterChain:
同時生成一個FilterChainProxy,過濾器代理類,後續登錄等各類請求的入口就是這個類了,能夠參見第二篇登錄流程介紹,debug開始的地方就是FilterChainProxy#doFilter,這裏真是一個典型的Filter模式實現!
至此,springSecurityFilterChain()執行完畢,返回的就是一個Filter:springSecurityFilterChain,做爲一個Bean!
生成完這個對象,初始化流程最重要的事情就完成了,後續主要就是在這個過濾器鏈中處理各類請求!