基於註解的配置(Java Configuration)從Spring Security 3.2開始就已經支持,本篇基於Spring boot註解的配置進行講解,若是須要基於XML配置(Security Namespace Configuration),可查閱Spring Security官網:https://docs.spring.io/spring-security/site/docs/5.1.5.RELEASE/reference/htmlsingle/#ns-confightml
基於Maven的Spring及Spring Boot配置再也不贅述,想要配置Spring Security,只須要@EnableWebSecurity註解。若是須要自定義一些配置,則須要和繼承WebSecurityConfigurerAdapter後,覆蓋某些方法:java
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { }
本節主要講經過@EnableWebSecurity的默認配置。下節來說經過繼承WebSecurityConfigurerAdapter的自定義配置。web
[2019/06/04 ADD]spring
在SpringBoot中,只要你加入spring-boot-starter-security包到項目中,即便不配置@EnableWebSecurity和WebSecurityConfigurerAdapter,SpringBoot也會自動給咱們添加這兩個配置。具體能夠看SpringBootWebSecurityConfiguration及WebSecurityEnablerConfiguration。安全
/** * The default configuration for web security. It relies on Spring Security's * content-negotiation strategy to determine what sort of authentication to use. If the * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off * completely and the users should specify all the bits that they want to configure as * part of the custom security configuration. * * @author Madhura Bhave * @since 2.0.0 */ @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 { } }
/** * If there is a bean of type WebSecurityConfigurerAdapter, this adds the * {@link EnableWebSecurity} annotation. This will make sure that the annotation is * present with default security auto-configuration and also if the user adds custom * security and forgets to add the annotation. If {@link EnableWebSecurity} has already * been added or if a bean with name {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has * been configured by the user, this will back-off. * * @author Madhura Bhave * @since 2.0.0 */ @Configuration @ConditionalOnBean(WebSecurityConfigurerAdapter.class) @ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN) @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @EnableWebSecurity public class WebSecurityEnablerConfiguration { }
@EnableWebSecurity雖然只是一個註解,但它實際上作了許多事。下面是叢Spring Security官網摘錄下來的:session
(1) Require authentication to every URL in your application #在你的應用程序中對每一個URL進行驗證 (2) Generate a login form for you #爲你生成一個登陸表單 (3) Allow the user with the Username user and the Password password to authenticate with form based authentication #容許使用用戶名和密碼使用驗證表單進行驗證 (4) Allow the user to logout #容許用戶登出 (5) CSRF attack prevention #CSRF attack攻擊防範 (6) Session Fixation protection #Session Fixation Session保護 (7) Security Header integration #安全Header集成 - HTTP Strict Transport Security for secure requests #嚴格的HTTP傳輸安全 - X-Content-Type-Options integration - Cache Control (can be overridden later by your application to allow caching of your static resources) - X-XSS-Protection integration - X-Frame-Options integration to help prevent Clickjacking (8) Integrate with the following Servlet API methods #如下Servlet API方法集成 - HttpServletRequest#getRemoteUser() - HttpServletRequest.html#getUserPrincipal() - HttpServletRequest.html#isUserInRole(java.lang.String) - HttpServletRequest.html#login(java.lang.String, java.lang.String) - HttpServletRequest.html#logout()
這麼多功能是怎麼實現的呢?咱們能夠查看@EnableWebSecurity的源碼,發現該註解會配置3個配置類:app
@EnableWebSecurity -> WebSecurityConfiguration.class,WebMvcSecurityConfiguration.class(condition is DispatcherServlet is present),OAuth2ImportSelector.class(condition is OAuth2ClientConfiguration is present)ide
@EnableWebSecurity -> @EnableGlobalAuthentication -> AuthenticationConfiguration.classspring-boot
其中,WebSecurityConfiguration是最主要的配置類。WebMvcSecurityConfiguration,OAuth2ImportSelector這裏再也不介紹。因爲在加載WebSecurityConfiguration的過程當中須要用到AuthenticationConfiguration Bean,因此,節下來咱們只講WebSecurityConfiguration。oop
下面是WebSecurityConfiguration類的源碼:
/** * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web * based security for Spring Security. It then exports the necessary beans. Customizations * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter} * and exposing it as a {@link Configuration} or implementing * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This * configuration is imported when using {@link EnableWebSecurity}. * * @see EnableWebSecurity * @see WebSecurity * * @author Rob Winch * @author Keesun Baik * @since 3.2 */ @Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { }
經過註釋能夠總結爲如下幾點:
(1)建立了WebSecurity及上節講的Security Filter Chain(List<SecurityFilterChain>)的代理對象FilterChainProxy Bean。
(2)建立了其餘一些必要的Bean。
(3)若是須要自定義WebSecurity的一些內容,能夠繼承WebSecurityConfigurerAdapter類或直接實現WebSecurityConfigurer接口,而後在去重寫相應方法。固然要用@Configuration聲明它爲配置類(@EnableWebSecurity中有@Configuration註解,不須要重複添加)。
(A)構建WebSecurity
初始化:WebSecurityConfiguration會先執行一個set方法(經過set方法注入的Bean List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers):
@Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; /** * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} * instances used to create the web configuration. * * @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) // [1.2] throws Exception { webSecurity = objectPostProcessor .postProcess(new WebSecurity(objectPostProcessor)); // [1.4] 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)) { // [1.3] 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); // [1.5] } this.webSecurityConfigurers = webSecurityConfigurers; } @Bean // [1.1] public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory); } }
[1.1] 用static先聲明一個autowiredWebSecurityConfigurersIgnoreParents Bean。
[1.2] 這個方法先經過@Value註解經過調用[1.1]的AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()獲取ApplicationContext中全部的WebSecurityConfigurer。具體能夠看一下AutowiredWebSecurityConfigurersIgnoreParents的源碼。
/** * A class used to get all the {@link WebSecurityConfigurer} instances from the current * {@link ApplicationContext} but ignoring the parent. * * @author Rob Winch * */ final class AutowiredWebSecurityConfigurersIgnoreParents { private final ConfigurableListableBeanFactory beanFactory; public AutowiredWebSecurityConfigurersIgnoreParents( ConfigurableListableBeanFactory beanFactory) { Assert.notNull(beanFactory, "beanFactory cannot be null"); this.beanFactory = beanFactory; } @SuppressWarnings({ "rawtypes", "unchecked" }) public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() { List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>(); Map<String, WebSecurityConfigurer> beansOfType = beanFactory .getBeansOfType(WebSecurityConfigurer.class); for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) { webSecurityConfigurers.add(entry.getValue()); } return webSecurityConfigurers; } }
一般狀況下這個WebSecurityConfigurer List只有一個元素,而且就是咱們本身繼承WebSecurityConfigurerAdapter配置的MySecurityConfig。
@EnableWebSecurity public class MySecurityConfig extends WebSecurityConfigurerAdapter { }
在SpringBoot自動配置的狀況下,若是咱們沒有繼承,則系統默認會使用SpringBootWebSecurityConfiguration的DefaultConfigurerAdapter。
/** * The default configuration for web security. It relies on Spring Security's * content-negotiation strategy to determine what sort of authentication to use. If the * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off * completely and the users should specify all the bits that they want to configure as * part of the custom security configuration. * * @author Madhura Bhave * @since 2.0.0 */ @ConditionalOnClass(WebSecurityConfigurerAdapter.class) // 有這個對象 @ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) // 可是沒有聲明這個bean @ConditionalOnWebApplication(type = Type.SERVLET) public class SpringBootWebSecurityConfiguration { @Configuration // 聲明一個DefaultConfigurerAdapter的配置Bean @Order(SecurityProperties.BASIC_AUTH_ORDER) static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { } }
[1.3] WebSecurityConfigurer若是有多個的狀況下,要對他們的@Order進行檢查,不能有相同的Order。
[1.4][1.5] 初始化WebSecurity,並將SecurityConfigurer(WebSecurityConfigurerAdapter)應用於此SecurityBuilder(WebSecurity),覆蓋徹底相同類的任何SecurityConfigurer。
構建:WebSecurity如何被初始化後,就開始構建,下面就是WebSecurityConfiguration中WebSecurity的構建方法,該方法聲明爲一個Bean,返回的其實就是上一節講的Spring Security Filter Chain。
/** * 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(); }
WebSecurity的構建過程很複雜,大概走了下面幾步流程:
[1.1] 調用AbstractSecurityBuilder.build()方法。
[1.2] 調用AbstractConfiguredSecurityBuilder.doBuild()方法(核心方法)。
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit(); // Do nothing if no child class override it. init(); // [1.2.1] buildState = BuildState.CONFIGURING; beforeConfigure(); // Do nothing if no child class override it. configure(); // [1.2.2] buildState = BuildState.BUILDING; O result = performBuild(); // [1.2.3] buildState = BuildState.BUILT; return result; } }
[1.2.1] 調用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法。這裏構建了HttpSecurity對象,把HttpSecurity添加到WebSecurity的securityFilterChainBuilders中(用於構建過濾器鏈)以及有一個共享對象FilterSecurityInterceptor。HttpSecurity的構建下面會重點介紹,這裏先略過。
public void init(final WebSecurity web) throws Exception { final HttpSecurity http = getHttp(); // 構建HttpSecurity對象 web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() {
// 把該對象添加到WebSecurity對象中用於接下來[1.2.3]構建過濾器鏈,能夠看下面WebSecurity的
// addSecurityFilterChainBuilder()方法 public void run() { FilterSecurityInterceptor securityInterceptor = http .getSharedObject(FilterSecurityInterceptor.class); web.securityInterceptor(securityInterceptor); } }); }
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements SecurityBuilder<Filter>, ApplicationContextAware { private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>(); /** * <p> * Adds builders to create {@link SecurityFilterChain} instances. * </p> * * <p> * Typically this method is invoked automatically within the framework from * {@link WebSecurityConfigurerAdapter#init(WebSecurity)} * </p> * * @param securityFilterChainBuilder the builder to use to create the * {@link SecurityFilterChain} instances * @return the {@link WebSecurity} for further customizations */ public WebSecurity addSecurityFilterChainBuilder( SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) { this.securityFilterChainBuilders.add(securityFilterChainBuilder); return this; } }
[1.2.2] 調用WebSecurityConfigurerAdapter的configure(WebSecurity web),可是什麼都沒作。咱們能夠經過繼承WebSecurityConfigurerAdapter來覆蓋該方法來自定義配置WebSecurity。
[1.2.3] 調用WebSecurity的performBuild()方法,用[1.2.1]的securityFilterChainBuilders構建過濾器鏈,並交給FilterChainProxy代理,並返回。值得一說的是,FilterChainProxy最終委託給DelegatingFilterProxy來執行,後者也是web.xml的Security配置項(來源於FilterChainProxy的類註釋)。
@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; }
(B)構建HttpSecurity
在WebSecurity的構建過程當中,在調用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法時(見上面的[1.2.1] ),調用WebSecurityConfigurerAdapter的getHttp()構建了HttpSecurity對象。
protected final HttpSecurity getHttp() throws Exception { if (http != null) { return http; } // The default strategy for publishing authentication events DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor .postProcess(new DefaultAuthenticationEventPublisher()); localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); AuthenticationManager authenticationManager = authenticationManager(); // [2.1] authenticationBuilder.parentAuthenticationManager(authenticationManager); authenticationBuilder.authenticationEventPublisher(eventPublisher);
// 插入一些共享對象(如UserDetailService,ApplicationContext)用於下面HttpSecurity的構建 Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); http = new HttpSecurity(objectPostProcessor, authenticationBuilder, sharedObjects); if (!disableDefaults) { // @formatter:off http .csrf().and() // [2.2] .addFilter(new WebAsyncManagerIntegrationFilter()) // [2.3] .exceptionHandling().and() // [2.4] .headers().and() // [2.5] .sessionManagement().and() // [2.6] .securityContext().and() // [2.7] .requestCache().and() // [2.8] .anonymous().and() // [2.9] .servletApi().and() // [2.10] .apply(new DefaultLoginPageConfigurer<>()).and() // [2.11] .logout(); // [2.12] // @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; }
protected void configure(HttpSecurity http) throws Exception { logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); http .authorizeRequests() // [2.13] .anyRequest().authenticated() .and() .formLogin().and() // [2.14] .httpBasic(); // [2.15] }
[2.1] 這裏實際上使用了配置類AuthenticationConfiguration Bean獲得了一個AuthenticationManager,這個過程當中,系統會自動配置這些認證對象:
ProviderManager -> AuthenticationManager
DaoAuthenticationProvider -> AuthenticationProvider
InMemoryUserDetailsManager -> UserDetailsService
User -> MutableUser -> MutableUserDetails -> UserDetails
其中,MutableUser代理了User對象及一個臨時的password。系統會自動生成1個MutableUser,name爲user(無ROLE)。
具體細節能夠看(C)部分。
[2.2] 添加配置器CsrfConfigurer(包含過濾器CsrfFilter *)對CSRF的支持。
[2.3] 添加過濾器WebAsyncManagerIntegrationFilter。
[2.4] 添加配置器ExceptionHandlingConfigurer(包含過濾器ExceptionTranslationFilter *)對異常處理的支持。
[2.5] 添加配置器HeadersConfigurer(包含過濾器HeaderWriterFilter *)支持Adds the Security HTTP headers to the response。
[2.6] 添加配置器SessionManagementConfigurer(包含過濾器SessionManagementFilter *)支持session管理。
[2.7] 添加配置器SecurityContextConfigurer(包含過濾器SecurityContextPersistenceFilter *)支持對SecurityContextHolder的配置。
[2.8] 添加配置器RequestCacheConfigurer(包含過濾器RequestCacheAwareFilter *)支持request cache。
[2.9] 添加配置器AnonymousConfigurer(包含過濾器AnonymousAuthenticationFilter *)支持Anonymous authentication。
[2.10] 添加配置器ServletApiConfigurer(包含過濾器SecurityContextHolderAwareRequestFilter *)支持更多Servlet API。
[2.11] 添加配置器DefaultLoginPageConfigurer(包含過濾器DefaultLoginPageGeneratingFilter,DefaultLogoutPageGeneratingFilter *)支持默認的login和logout。
[2.12] 添加配置器LogoutConfigurer(包含過濾器LogoutFilter *)支持logout。
[2.13] 添加配置器ExpressionUrlAuthorizationConfigurer -> AbstractInterceptUrlConfigurer(包含過濾器FilterSecurityInterceptor *)支持URL based authorization。
[2.14] 添加配置器FormLoginConfigurer -> AbstractAuthenticationFilterConfigurer(包含過濾器UsernamePasswordAuthenticationFilter *)支持經過login認證。
[2.15] 添加配置器HttpBasicConfigurer(包含過濾器BasicAuthenticationFilter *)支持HTTP basic based authentication。
[*] 該過濾器在[1.2.3]中securityFilterChainBuilder.build()時經過調用該配置器的configure()方法把過濾器加到HttpSecurity中。
以上的15個過濾器就和章節2Spring Security(2):過濾器鏈(filter chain)的介紹中的15個過濾器一一對應。
(C)構建AuthenticationManager及自動配置時自動建立認證對象
由[2.1]可知,Spring Security會自動建立一些認證對象。那麼它們是怎麼建立出來的呢?
在[2.1]中,調用了WebSecurityConfigurerAdapter.authenticationManager()方法。從下面的代碼能夠看到,因爲咱們並未配置自定義的AuthenticationManagerBuilder(變量名是localConfigureAuthenticationBldr),因此咱們用注入的AuthenticationConfiguration,調用AuthenticationConfiguration的getAuthenticationManager()方法,獲得了AuthenticationManager對象。
WebSecurityConfigurerAdapter.authenticationManager():
private AuthenticationConfiguration authenticationConfiguration; /** * Gets the {@link AuthenticationManager} to use. The default strategy is if * {@link #configure(AuthenticationManagerBuilder)} method is overridden to use the * {@link AuthenticationManagerBuilder} that was passed in. Otherwise, autowire the * {@link AuthenticationManager} by type. * * @return the {@link AuthenticationManager} to use * @throws Exception */ protected AuthenticationManager authenticationManager() throws Exception { if (!authenticationManagerInitialized) { configure(localConfigureAuthenticationBldr); if (disableLocalConfigureAuthenticationBldr) { authenticationManager = authenticationConfiguration .getAuthenticationManager(); // execute here } else { authenticationManager = localConfigureAuthenticationBldr.build(); } authenticationManagerInitialized = true; } return authenticationManager; } @Autowired public void setAuthenticationConfiguration( AuthenticationConfiguration authenticationConfiguration) { this.authenticationConfiguration = authenticationConfiguration; }
AuthenticationConfiguration.getAuthenticationManager():
public AuthenticationManager getAuthenticationManager() throws Exception { if (this.authenticationManagerInitialized) { return this.authenticationManager; }
// [3.1] AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder( this.objectPostProcessor, this.applicationContext); if (this.buildingAuthenticationManager.getAndSet(true)) { return new AuthenticationManagerDelegator(authBuilder); }
// [3.2] for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { authBuilder.apply(config); }
// [3.3] authenticationManager = authBuilder.build(); if (authenticationManager == null) { authenticationManager = getAuthenticationManagerBean(); } this.authenticationManagerInitialized = true; return authenticationManager; }
[3.1] 調用AuthenticationConfiguration.authenticationManagerBuilder()方法,使用了一個默認的AuthenticationManagerBuilder實現類DefaultPasswordEncoderAuthenticationManagerBuilder(同時這也是一個Bean)。
@Bean public AuthenticationManagerBuilder authenticationManagerBuilder( ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) { LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class); DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); if (authenticationEventPublisher != null) { result.authenticationEventPublisher(authenticationEventPublisher); } return result; }
[3.2] 這個globalAuthConfigurers其實就是AuthenticationConfiguration中聲明的3個static bean。因爲是static的,因此最先加載。
@Bean public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( ApplicationContext context) {return new EnableGlobalAuthenticationAutowiredConfigurer(context); } @Bean public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {return new InitializeUserDetailsBeanManagerConfigurer(context); } @Bean public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {return new InitializeAuthenticationProviderBeanManagerConfigurer(context); }
[3.3] build()方法會調用AbstractConfiguredSecurityBuilder.doBuild()方法,最終會前後調用[3.2]的3個configurer的init()方法和configure()方法,及調用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父類AuthenticationManagerBuilder的performBuild()方法。
@Override protected final O doBuild() throws Exception { synchronized (configurers) { buildState = BuildState.INITIALIZING; beforeInit();
// 循環調用[3.2]的3個configurer的init()方法(有些可能沒有) init(); buildState = BuildState.CONFIGURING; beforeConfigure();
// 循環調用[3.2]的3個configurer的configure()方法(有些可能沒有) configure(); buildState = BuildState.BUILDING; // 調用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父類AuthenticationManagerBuilder的performBuild()方法 O result = performBuild(); buildState = BuildState.BUILT; return result; } }
經過調用這些方法自動生成了:
ProviderManager -> AuthenticationManager
DaoAuthenticationProvider -> AuthenticationProvider
InMemoryUserDetailsManager -> UserDetailsService
User -> MutableUser -> MutableUserDetails -> UserDetails
(C.1)User & InMemoryUserDetailsManager & DaoAuthenticationProvider:在InitializeUserDetailsBeanManagerConfigurer.config()中,及自動配置類UserDetailsServiceAutoConfiguration中建立
InitializeUserDetailsBeanManagerConfigurer:
@Override public void configure(AuthenticationManagerBuilder auth) throws Exception { if (auth.isConfigured()) { return; }
// 若是使用了Spring Boot, 執行這一步時會使用自動配置,
// 從UserDetailsServiceAutoConfiguration中Lazy load一個InMemoryUserDetailsManager UserDetailsService userDetailsService = getBeanOrNull( UserDetailsService.class); if (userDetailsService == null) { return; } PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); // 建立DaoAuthenticationProvider,並把UserDetailsService放入其中 DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); provider.setUserDetailsService(userDetailsService); if (passwordEncoder != null) { provider.setPasswordEncoder(passwordEncoder); } if (passwordManager != null) { provider.setUserDetailsPasswordService(passwordManager); } provider.afterPropertiesSet(); auth.authenticationProvider(provider); }
UserDetailsServiceAutoConfiguration:須要注意的是,Spring Bean容器中,若是同時沒有AuthenticationManager,AuthenticationProvider,UserDetailsService時,該自動配置纔會生效。(To also switch off the UserDetailsService configuration, you can add a bean of type UserDetailsService, AuthenticationProvider, or AuthenticationManager.)
@Configuration @ConditionalOnClass(AuthenticationManager.class) @ConditionalOnBean(ObjectPostProcessor.class) @ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class }) public class UserDetailsServiceAutoConfiguration { private static final String NOOP_PASSWORD_PREFIX = "{noop}"; private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern .compile("^\\{.+}.*$"); private static final Log logger = LogFactory .getLog(UserDetailsServiceAutoConfiguration.class); @Bean @ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository") @Lazy public InMemoryUserDetailsManager inMemoryUserDetailsManager( SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) { SecurityProperties.User user = properties.getUser(); List<String> roles = user.getRoles(); return new InMemoryUserDetailsManager(User.withUsername(user.getName()) .password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())) .roles(StringUtils.toStringArray(roles)).build()); } private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) { String password = user.getPassword(); if (user.isPasswordGenerated()) { logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword())); } if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) { return password; } return NOOP_PASSWORD_PREFIX + password; } }
(C.2)ProviderManager :在AuthenticationManagerBuilder.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; }
總結:
最後上兩張類圖,分別是SecurityBuilder和SecurityConfiger。流程實際上就是先調用Builder的add()方法或apply()方法添加和維護一個SecurityConfiger List。最後經過調用Builder的build()方法(其實是AbstractConfiguredSecurityBuilder的doBuild()方法),調用SecurityConfiger的init()方法和configure()方法構建WebSecurity及過濾器鏈。