基于注解的配置(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-config
基于Maven的Spring及Spring Boot配置不再赘述,想要配置Spring Security,只需要**@EnableWebSecurity注解。如果需要自定义一些配置,则需要和继承WebSecurityConfigurerAdapter后,覆盖某些方法**:
1@EnableWebSecurity 2public class MySecurityConfig extends WebSecurityConfigurerAdapter { }
本节主要讲通过@EnableWebSecurity的默认配置。下节来讲通过继承WebSecurityConfigurerAdapter的自定义配置。
[2019/06/04 ADD]
在SpringBoot中,只要你加入spring-boot-starter-security包到项目中,即使不配置@EnableWebSecurity和WebSecurityConfigurerAdapter,SpringBoot也会自动给我们添加这两个配置。具体可以看SpringBootWebSecurityConfiguration及WebSecurityEnablerConfiguration。
1/** 2 * The default configuration for web security. It relies on Spring Security's 3 * content-negotiation strategy to determine what sort of authentication to use. If the 4 * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off 5 * completely and the users should specify all the bits that they want to configure as 6 * part of the custom security configuration. 7 * 8 * @author Madhura Bhave 9 * @since 2.0.0 10 */ 11@Configuration 12@ConditionalOnClass(WebSecurityConfigurerAdapter.class) 13@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) 14@ConditionalOnWebApplication(type = Type.SERVLET) 15public class SpringBootWebSecurityConfiguration { 16 @Configuration 17 @Order(SecurityProperties.BASIC_AUTH_ORDER) 18 static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { 19 } 20} 21 22/** 23 * If there is a bean of type WebSecurityConfigurerAdapter, this adds the 24 * {@link EnableWebSecurity} annotation. This will make sure that the annotation is 25 * present with default security auto-configuration and also if the user adds custom 26 * security and forgets to add the annotation. If {@link EnableWebSecurity} has already 27 * been added or if a bean with name {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has 28 * been configured by the user, this will back-off. 29 * 30 * @author Madhura Bhave 31 * @since 2.0.0 32 */ 33@Configuration 34@ConditionalOnBean(WebSecurityConfigurerAdapter.class) 35@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN) 36@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) 37@EnableWebSecurity 38public class WebSecurityEnablerConfiguration { 39}
@EnableWebSecurity虽然只是一个注解,但它实际上做了许多事。下面是丛Spring Security官网摘录下来的:
1(1) Require authentication to every URL in your application #在你的应用程序中对每个URL进行验证 2(2) Generate a login form for you #为你生成一个登录表单 3(3) Allow the user with the Username user and the Password password to authenticate with form based authentication #允许使用用户名和密码使用验证表单进行验证 4(4) Allow the user to logout #允许用户登出 5(5) CSRF attack prevention #CSRF attack攻击防范 6(6) Session Fixation protection #Session Fixation Session保护 7(7) Security Header integration #安全Header集成 8 - HTTP Strict Transport Security for secure requests #严格的HTTP传输安全 9 - X-Content-Type-Options integration 10 - Cache Control (can be overridden later by your application to allow caching of your static resources) 11 - X-XSS-Protection integration 12 - X-Frame-Options integration to help prevent Clickjacking 13(8) Integrate with the following Servlet API methods #以下Servlet API方法集成 14 - HttpServletRequest#getRemoteUser() 15 - HttpServletRequest.html#getUserPrincipal() 16 - HttpServletRequest.html#isUserInRole(java.lang.String) 17 - HttpServletRequest.html#login(java.lang.String, java.lang.String) 18 - HttpServletRequest.html#logout()
这么多功能是怎么实现的呢?我们可以查看@EnableWebSecurity的源码,发现该注解会配置3个配置类:
@EnableWebSecurity -> WebSecurityConfiguration.class,WebMvcSecurityConfiguration.class(condition is DispatcherServlet is present),OAuth2ImportSelector.class(condition is OAuth2ClientConfiguration is present)
@EnableWebSecurity -> @EnableGlobalAuthentication -> AuthenticationConfiguration.class
其中,WebSecurityConfiguration是最主要的配置类。WebMvcSecurityConfiguration,OAuth2ImportSelector这里不再介绍。由于在加载WebSecurityConfiguration的过程中需要用到AuthenticationConfiguration Bean,所以,节下来我们只讲WebSecurityConfiguration。
下面是WebSecurityConfiguration类的源码:
1/** 2 * Uses a {@link WebSecurity} to create the {@link FilterChainProxy} that performs the web 3 * based security for Spring Security. It then exports the necessary beans. Customizations 4 * can be made to {@link WebSecurity} by extending {@link WebSecurityConfigurerAdapter} 5 * and exposing it as a {@link Configuration} or implementing 6 * {@link WebSecurityConfigurer} and exposing it as a {@link Configuration}. This 7 * configuration is imported when using {@link EnableWebSecurity}. 8 * 9 * @see EnableWebSecurity 10 * @see WebSecurity 11 * 12 * @author Rob Winch 13 * @author Keesun Baik 14 * @since 3.2 15 */ 16@Configuration 17public 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):
1@Configuration 2public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { 3 4 private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers; 5 6 /** 7 * Sets the {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} 8 * instances used to create the web configuration. 9 * 10 * @param objectPostProcessor the {@link ObjectPostProcessor} used to create a 11 * {@link WebSecurity} instance 12 * @param webSecurityConfigurers the 13 * {@code <SecurityConfigurer<FilterChainProxy, WebSecurityBuilder>} instances used to 14 * create the web configuration 15 * @throws Exception 16 */ 17 @Autowired(required = false) 18 public void setFilterChainProxySecurityConfigurer( 19 ObjectPostProcessor<Object> objectPostProcessor, 20 @Value("#{@autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()}") List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers) // [1.2] 21 throws Exception { 22 webSecurity = objectPostProcessor 23 .postProcess(new WebSecurity(objectPostProcessor)); // [1.4] 24 if (debugEnabled != null) { 25 webSecurity.debug(debugEnabled); 26 } 27 28 Collections.sort(webSecurityConfigurers, AnnotationAwareOrderComparator.INSTANCE); 29 30 Integer previousOrder = null; 31 Object previousConfig = null; 32 for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) { 33 Integer order = AnnotationAwareOrderComparator.lookupOrder(config); 34 if (previousOrder != null && previousOrder.equals(order)) { // [1.3] 35 throw new IllegalStateException( 36 "@Order on WebSecurityConfigurers must be unique. Order of " 37 + order + " was already used on " + previousConfig + ", so it cannot be used on " 38 + config + " too."); 39 } 40 previousOrder = order; 41 previousConfig = config; 42 } 43 for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) { 44 webSecurity.apply(webSecurityConfigurer); // [1.5] 45 } 46 this.webSecurityConfigurers = webSecurityConfigurers; 47 } 48 49 @Bean // [1.1] 50 public static AutowiredWebSecurityConfigurersIgnoreParents autowiredWebSecurityConfigurersIgnoreParents( 51 ConfigurableListableBeanFactory beanFactory) { 52 return new AutowiredWebSecurityConfigurersIgnoreParents(beanFactory); 53 } 54}
[1.1] 用static先声明一个autowiredWebSecurityConfigurersIgnoreParents Bean。
[1.2] 这个方法先通过@Value注解通过调用[1.1]的AutowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()获取ApplicationContext中所有的WebSecurityConfigurer。具体可以看一下AutowiredWebSecurityConfigurersIgnoreParents的源码。
1/** 2 * A class used to get all the {@link WebSecurityConfigurer} instances from the current 3 * {@link ApplicationContext} but ignoring the parent. 4 * 5 * @author Rob Winch 6 * 7 */ 8final class AutowiredWebSecurityConfigurersIgnoreParents { 9 10 private final ConfigurableListableBeanFactory beanFactory; 11 12 public AutowiredWebSecurityConfigurersIgnoreParents( 13 ConfigurableListableBeanFactory beanFactory) { 14 Assert.notNull(beanFactory, "beanFactory cannot be null"); 15 this.beanFactory = beanFactory; 16 } 17 18 @SuppressWarnings({ "rawtypes", "unchecked" }) 19 public List<SecurityConfigurer<Filter, WebSecurity>> getWebSecurityConfigurers() { 20 List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new ArrayList<SecurityConfigurer<Filter, WebSecurity>>(); 21 Map<String, WebSecurityConfigurer> beansOfType = beanFactory 22 .getBeansOfType(WebSecurityConfigurer.class); 23 for (Entry<String, WebSecurityConfigurer> entry : beansOfType.entrySet()) { 24 webSecurityConfigurers.add(entry.getValue()); 25 } 26 return webSecurityConfigurers; 27 } 28}
通常情况下这个WebSecurityConfigurer List只有一个元素,并且就是我们自己继承WebSecurityConfigurerAdapter配置的MySecurityConfig。
1@EnableWebSecurity 2public class MySecurityConfig extends WebSecurityConfigurerAdapter { }
在SpringBoot自动配置的情况下,如果我们没有继承,则系统默认会使用SpringBootWebSecurityConfiguration的DefaultConfigurerAdapter。
1/** 2 * The default configuration for web security. It relies on Spring Security's 3 * content-negotiation strategy to determine what sort of authentication to use. If the 4 * user specifies their own {@link WebSecurityConfigurerAdapter}, this will back-off 5 * completely and the users should specify all the bits that they want to configure as 6 * part of the custom security configuration. 7 * 8 * @author Madhura Bhave 9 * @since 2.0.0 10 */ 11@ConditionalOnClass(WebSecurityConfigurerAdapter.class) // 有这个对象 12@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class) // 但是没有声明这个bean 13@ConditionalOnWebApplication(type = Type.SERVLET) 14public class SpringBootWebSecurityConfiguration { 15 16 @Configuration // 声明一个DefaultConfigurerAdapter的配置Bean 17 @Order(SecurityProperties.BASIC_AUTH_ORDER) 18 static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter { 19 20 } 21}
[1.3] WebSecurityConfigurer如果有多个的情况下,要对他们的@Order进行检查,不能有相同的Order。
[1.4][1.5] 初始化WebSecurity,并将SecurityConfigurer(WebSecurityConfigurerAdapter)应用于此SecurityBuilder(WebSecurity),覆盖完全相同类的任何SecurityConfigurer。
构建:WebSecurity如何被初始化后,就开始构建,下面就是WebSecurityConfiguration中WebSecurity的构建方法,该方法声明为一个Bean,返回的其实就是上一节讲的Spring Security Filter Chain。
1/** 2 * Creates the Spring Security Filter Chain 3 * @return the {@link Filter} that represents the security filter chain 4 * @throws Exception 5 */ 6 @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) 7 public Filter springSecurityFilterChain() throws Exception { 8 boolean hasConfigurers = webSecurityConfigurers != null 9 && !webSecurityConfigurers.isEmpty(); 10 if (!hasConfigurers) { 11 WebSecurityConfigurerAdapter adapter = objectObjectPostProcessor 12 .postProcess(new WebSecurityConfigurerAdapter() { 13 }); 14 webSecurity.apply(adapter); 15 } 16 return webSecurity.build(); 17 }
WebSecurity的构建过程很复杂,大概走了下面几步流程:
[1.1] 调用AbstractSecurityBuilder.build()方法。
[1.2] 调用AbstractConfiguredSecurityBuilder.doBuild()方法(核心方法)。
1 @Override 2 protected final O doBuild() throws Exception { 3 synchronized (configurers) { 4 buildState = BuildState.INITIALIZING; 5 6 beforeInit(); // Do nothing if no child class override it. 7 init(); // [1.2.1] 8 9 buildState = BuildState.CONFIGURING; 10 11 beforeConfigure(); // Do nothing if no child class override it. 12 configure(); // [1.2.2] 13 14 buildState = BuildState.BUILDING; 15 16 O result = performBuild(); // [1.2.3] 17 18 buildState = BuildState.BUILT; 19 20 return result; 21 } 22 }
[1.2.1] 调用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法。这里构建了HttpSecurity对象,把HttpSecurity添加到WebSecurity的securityFilterChainBuilders中(用于构建过滤器链)以及有一个共享对象FilterSecurityInterceptor。HttpSecurity的构建下面会重点介绍,这里先略过。
1public void init(final WebSecurity web) throws Exception { 2 final HttpSecurity http = getHttp(); // 构建HttpSecurity对象 3 web.addSecurityFilterChainBuilder(http).postBuildAction(new Runnable() { // 把该对象添加到WebSecurity对象中用于接下来[1.2.3]构建过滤器链,可以看下面WebSecurity的 // addSecurityFilterChainBuilder()方法 4 public void run() { 5 FilterSecurityInterceptor securityInterceptor = http 6 .getSharedObject(FilterSecurityInterceptor.class); 7 web.securityInterceptor(securityInterceptor); 8 } 9 }); 10 } 11 12public final class WebSecurity extends 13 AbstractConfiguredSecurityBuilder<Filter, WebSecurity> implements 14 SecurityBuilder<Filter>, ApplicationContextAware { 15 16 private final List<SecurityBuilder<? extends SecurityFilterChain>> securityFilterChainBuilders = new ArrayList<SecurityBuilder<? extends SecurityFilterChain>>(); 17 18 /** 19 * <p> 20 * Adds builders to create {@link SecurityFilterChain} instances. 21 * </p> 22 * 23 * <p> 24 * Typically this method is invoked automatically within the framework from 25 * {@link WebSecurityConfigurerAdapter#init(WebSecurity)} 26 * </p> 27 * 28 * @param securityFilterChainBuilder the builder to use to create the 29 * {@link SecurityFilterChain} instances 30 * @return the {@link WebSecurity} for further customizations 31 */ 32 public WebSecurity addSecurityFilterChainBuilder( 33 SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder) { 34 this.securityFilterChainBuilders.add(securityFilterChainBuilder); 35 return this; 36 } 37}
[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的类注释)。
1 @Override 2 protected Filter performBuild() throws Exception { 3 Assert.state( 4 !securityFilterChainBuilders.isEmpty(), 5 () -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. " 6 + "Typically this done by adding a @Configuration that extends WebSecurityConfigurerAdapter. " 7 + "More advanced users can invoke " 8 + WebSecurity.class.getSimpleName() 9 + ".addSecurityFilterChainBuilder directly"); 10 int chainSize = ignoredRequests.size() + securityFilterChainBuilders.size(); 11 List<SecurityFilterChain> securityFilterChains = new ArrayList<>( 12 chainSize); 13 for (RequestMatcher ignoredRequest : ignoredRequests) { 14 securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest)); 15 } 16 for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : securityFilterChainBuilders) { 17 securityFilterChains.add(securityFilterChainBuilder.build()); 18 } 19 FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains); 20 if (httpFirewall != null) { 21 filterChainProxy.setFirewall(httpFirewall); 22 } 23 filterChainProxy.afterPropertiesSet(); 24 25 Filter result = filterChainProxy; 26 if (debugEnabled) { 27 logger.warn("\n\n" 28 + "********************************************************************\n" 29 + "********** Security debugging is enabled. *************\n" 30 + "********** This may include sensitive information. *************\n" 31 + "********** Do not use in a production system! *************\n" 32 + "********************************************************************\n\n"); 33 result = new DebugFilter(filterChainProxy); 34 } 35 postBuildAction.run(); 36 return result; 37 }
(B)构建HttpSecurity
在WebSecurity的构建过程中,在调用WebSecurityConfigurerAdapter的init(final WebSecurity web)方法时(见上面的[1.2.1] ),调用WebSecurityConfigurerAdapter的getHttp()构建了HttpSecurity对象。
1protected final HttpSecurity getHttp() throws Exception { 2 if (http != null) { 3 return http; 4 } 5 // The default strategy for publishing authentication events 6 DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor 7 .postProcess(new DefaultAuthenticationEventPublisher()); 8 localConfigureAuthenticationBldr.authenticationEventPublisher(eventPublisher); 9 10 AuthenticationManager authenticationManager = authenticationManager(); // [2.1] 11 authenticationBuilder.parentAuthenticationManager(authenticationManager); 12 authenticationBuilder.authenticationEventPublisher(eventPublisher); // 插入一些共享对象(如UserDetailService,ApplicationContext)用于下面HttpSecurity的构建 13 Map<Class<? extends Object>, Object> sharedObjects = createSharedObjects(); 14 15 http = new HttpSecurity(objectPostProcessor, authenticationBuilder, 16 sharedObjects); 17 if (!disableDefaults) { 18 // @formatter:off 19 http 20 .csrf().and() // [2.2] 21 .addFilter(new WebAsyncManagerIntegrationFilter()) // [2.3] 22 .exceptionHandling().and() // [2.4] 23 .headers().and() // [2.5] 24 .sessionManagement().and() // [2.6] 25 .securityContext().and() // [2.7] 26 .requestCache().and() // [2.8] 27 .anonymous().and() // [2.9] 28 .servletApi().and() // [2.10] 29 .apply(new DefaultLoginPageConfigurer<>()).and() // [2.11] 30 .logout(); // [2.12] 31 // @formatter:on 32 ClassLoader classLoader = this.context.getClassLoader(); 33 List<AbstractHttpConfigurer> defaultHttpConfigurers = 34 SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, classLoader); 35 36 for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) { 37 http.apply(configurer); 38 } 39 } 40 configure(http); 41 return http; 42 } 43 44protected void configure(HttpSecurity http) throws Exception { 45 logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity)."); 46 47 http 48 .authorizeRequests() // [2.13] 49 .anyRequest().authenticated() 50 .and() 51 .formLogin().and() // [2.14] 52 .httpBasic(); // [2.15] 53 }
[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():
1private AuthenticationConfiguration authenticationConfiguration; 2 3 /** 4 * Gets the {@link AuthenticationManager} to use. The default strategy is if 5 * {@link #configure(AuthenticationManagerBuilder)} method is overridden to use the 6 * {@link AuthenticationManagerBuilder} that was passed in. Otherwise, autowire the 7 * {@link AuthenticationManager} by type. 8 * 9 * @return the {@link AuthenticationManager} to use 10 * @throws Exception 11 */ 12 protected AuthenticationManager authenticationManager() throws Exception { 13 if (!authenticationManagerInitialized) { 14 configure(localConfigureAuthenticationBldr); 15 if (disableLocalConfigureAuthenticationBldr) { 16 authenticationManager = authenticationConfiguration 17 .getAuthenticationManager(); // execute here 18 } 19 else { 20 authenticationManager = localConfigureAuthenticationBldr.build(); 21 } 22 authenticationManagerInitialized = true; 23 } 24 return authenticationManager; 25 } 26 27 @Autowired 28 public void setAuthenticationConfiguration( 29 AuthenticationConfiguration authenticationConfiguration) { 30 this.authenticationConfiguration = authenticationConfiguration; 31 }
AuthenticationConfiguration.getAuthenticationManager():
1public AuthenticationManager getAuthenticationManager() throws Exception { 2 if (this.authenticationManagerInitialized) { 3 return this.authenticationManager; 4 } // [3.1] 5 AuthenticationManagerBuilder authBuilder = authenticationManagerBuilder( 6 this.objectPostProcessor, this.applicationContext); 7 if (this.buildingAuthenticationManager.getAndSet(true)) { 8 return new AuthenticationManagerDelegator(authBuilder); 9 } 10 // [3.2] 11 for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) { 12 authBuilder.apply(config); 13 } 14 // [3.3] 15 authenticationManager = authBuilder.build(); 16 17 if (authenticationManager == null) { 18 authenticationManager = getAuthenticationManagerBean(); 19 } 20 21 this.authenticationManagerInitialized = true; 22 return authenticationManager; 23 }
[3.1] 调用AuthenticationConfiguration.authenticationManagerBuilder()方法,使用了一个默认的AuthenticationManagerBuilder实现类DefaultPasswordEncoderAuthenticationManagerBuilder(同时这也是一个Bean)。
1 @Bean 2 public AuthenticationManagerBuilder authenticationManagerBuilder( 3 ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) { 4 LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context); 5 AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class); 6 7 DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder); 8 if (authenticationEventPublisher != null) { 9 result.authenticationEventPublisher(authenticationEventPublisher); 10 } 11 return result; 12 }
[3.2] 这个globalAuthConfigurers其实就是AuthenticationConfiguration中声明的3个static bean。由于是static的,所以最早加载。
1 @Bean 2 public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer( 3 ApplicationContext context) {return new EnableGlobalAuthenticationAutowiredConfigurer(context); 4 } 5 6 @Bean 7 public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {return new InitializeUserDetailsBeanManagerConfigurer(context); 8 } 9 10 @Bean 11 public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {return new InitializeAuthenticationProviderBeanManagerConfigurer(context); 12 }
[3.3] build()方法会调用AbstractConfiguredSecurityBuilder.doBuild()方法,最终会先后调用[3.2]的3个configurer的init()方法和configure()方法,及调用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父类AuthenticationManagerBuilder的performBuild()方法。
1 @Override 2 protected final O doBuild() throws Exception { 3 synchronized (configurers) { 4 buildState = BuildState.INITIALIZING; 5 6 beforeInit(); // 循环调用[3.2]的3个configurer的init()方法(有些可能没有) 7 init(); 8 9 buildState = BuildState.CONFIGURING; 10 11 beforeConfigure(); // 循环调用[3.2]的3个configurer的configure()方法(有些可能没有) 12 configure(); 13 14 buildState = BuildState.BUILDING; 15 // 调用[3.1]DefaultPasswordEncoderAuthenticationManagerBuilder的父类AuthenticationManagerBuilder的performBuild()方法 16 O result = performBuild(); 17 18 buildState = BuildState.BUILT; 19 20 return result; 21 } 22 }
通过调用这些方法自动生成了:
ProviderManager -> AuthenticationManager
DaoAuthenticationProvider -> AuthenticationProvider
InMemoryUserDetailsManager -> UserDetailsService
User -> MutableUser -> MutableUserDetails -> UserDetails
(C.1)User & InMemoryUserDetailsManager & DaoAuthenticationProvider:在InitializeUserDetailsBeanManagerConfigurer.config()中,及自动配置类UserDetailsServiceAutoConfiguration中创建
InitializeUserDetailsBeanManagerConfigurer:
1 @Override 2 public void configure(AuthenticationManagerBuilder auth) throws Exception { 3 if (auth.isConfigured()) { 4 return; 5 } // 如果使用了Spring Boot, 执行这一步时会使用自动配置, // 从UserDetailsServiceAutoConfiguration中Lazy load一个InMemoryUserDetailsManager 6 UserDetailsService userDetailsService = getBeanOrNull( 7 UserDetailsService.class); 8 if (userDetailsService == null) { 9 return; 10 } 11 12 PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class); 13 UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class); 14 // 创建DaoAuthenticationProvider,并把UserDetailsService放入其中 15 DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); 16 provider.setUserDetailsService(userDetailsService); 17 if (passwordEncoder != null) { 18 provider.setPasswordEncoder(passwordEncoder); 19 } 20 if (passwordManager != null) { 21 provider.setUserDetailsPasswordService(passwordManager); 22 } 23 provider.afterPropertiesSet(); 24 25 auth.authenticationProvider(provider); 26 }
UserDetailsServiceAutoConfiguration:需要注意的是,Spring Bean容器中,如果同时没有AuthenticationManager,AuthenticationProvider,UserDetailsService时,该自动配置才会生效。(To also switch off the UserDetailsService configuration, you can add a bean of type UserDetailsService, AuthenticationProvider, or AuthenticationManager.)
1@Configuration 2@ConditionalOnClass(AuthenticationManager.class) 3@ConditionalOnBean(ObjectPostProcessor.class) 4@ConditionalOnMissingBean({ AuthenticationManager.class, AuthenticationProvider.class, 5 UserDetailsService.class }) 6public class UserDetailsServiceAutoConfiguration { 7 8 private static final String NOOP_PASSWORD_PREFIX = "{noop}"; 9 10 private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern 11 .compile("^\\{.+}.*$"); 12 13 private static final Log logger = LogFactory 14 .getLog(UserDetailsServiceAutoConfiguration.class); 15 16 @Bean 17 @ConditionalOnMissingBean(type = "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository") 18 @Lazy 19 public InMemoryUserDetailsManager inMemoryUserDetailsManager( 20 SecurityProperties properties, 21 ObjectProvider<PasswordEncoder> passwordEncoder) { 22 SecurityProperties.User user = properties.getUser(); 23 List<String> roles = user.getRoles(); 24 return new InMemoryUserDetailsManager(User.withUsername(user.getName()) 25 .password(getOrDeducePassword(user, passwordEncoder.getIfAvailable())) 26 .roles(StringUtils.toStringArray(roles)).build()); 27 } 28 29 private String getOrDeducePassword(SecurityProperties.User user, 30 PasswordEncoder encoder) { 31 String password = user.getPassword(); 32 if (user.isPasswordGenerated()) { 33 logger.info(String.format("%n%nUsing generated security password: %s%n", 34 user.getPassword())); 35 } 36 if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) { 37 return password; 38 } 39 return NOOP_PASSWORD_PREFIX + password; 40 } 41 42}
(C.2)ProviderManager :在AuthenticationManagerBuilder.performBuild()中创建
1 @Override 2 protected ProviderManager performBuild() throws Exception { 3 if (!isConfigured()) { 4 logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null."); 5 return null; 6 } 7 ProviderManager providerManager = new ProviderManager(authenticationProviders, 8 parentAuthenticationManager); 9 if (eraseCredentials != null) { 10 providerManager.setEraseCredentialsAfterAuthentication(eraseCredentials); 11 } 12 if (eventPublisher != null) { 13 providerManager.setAuthenticationEventPublisher(eventPublisher); 14 } 15 providerManager = postProcess(providerManager); 16 return providerManager; 17 }
总结:
最后上两张类图,分别是SecurityBuilder和SecurityConfiger。流程实际上就是先调用Builder的add()方法或apply()方法添加和维护一个SecurityConfiger List。最后通过调用Builder的build()方法(实际上是AbstractConfiguredSecurityBuilder的doBuild()方法),调用SecurityConfiger的init()方法和configure()方法构建WebSecurity及过滤器链。

