Spring Security(3):配置与自动配置的介绍及源码分析
基于注解的配置(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后,覆盖某些方法:
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter { }
本节主要讲通过@EnableWebSecurity的默认配置。下节来讲通过继承WebSecurityConfigurerAdapter的自定义配置。
[2019/06/04 ADD]
在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官网摘录下来的:
(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个配置类:
@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类的源码:
/**
* 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及过滤器链。
Spring Security(3):配置与自动配置的介绍及源码分析的更多相关文章
- spring事务概念与获取事务时事务传播行为源码分析
一.事务状态:org.springframework.transaction.TransactionStatus isNewTransaction 是否是新事务 hasSavepoint 是否有保存点 ...
- Spring Security(1):认证和授权的核心组件介绍及源码分析
Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方式的安全框架.它包括认证(Authentication)和授权(Authorization)两个部 ...
- Spring Security,没有看起来那么复杂(附源码)
权限管理是每个项目必备的功能,只是各自要求的复杂程度不同,简单的项目可能一个 Filter 或 Interceptor 就解决了,复杂一点的就可能会引入安全框架,如 Shiro, Spring Sec ...
- SpringCloudAlibaba注册中心与配置中心之利器Nacos实战与源码分析(上)
不断踩坑并解决问题是每个程序员进阶到资深的必要经历并以此获得满足感,而不断阅读开源项目源码和总结思想是每个架构师成长最佳途径.本篇拉开SpringCloud Alibaba最新版本实战和原理序幕,以工 ...
- Spring AOP介绍及源码分析
转自:http://www.uml.org.cn/j2ee/201301102.asp 软件开发经历了从汇编语言到高级语言和从过程化编程到面向对象编程:前者是为了提高开发效率,而后者则使用了归纳法,把 ...
- Spring Security(四) —— 核心过滤器源码分析
摘要: 原创出处 https://www.cnkirito.moe/spring-security-4/ 「老徐」欢迎转载,保留摘要,谢谢! 4 过滤器详解 前面的部分,我们关注了Spring Sec ...
- Spring Boot启动命令参数详解及源码分析
使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...
- Spring Security(08)——intercept-url配置
http://elim.iteye.com/blog/2161056 Spring Security(08)--intercept-url配置 博客分类: spring Security Spring ...
- Spring boot运行原理-自定义自动配置类
在前面SpringBoot的文章中介绍了SpringBoot的基本配置,今天我们将给大家讲一讲SpringBoot的运行原理,然后根据原理我们自定义一个starter pom. 本章对于后续继续学习S ...
随机推荐
- 部署WCF Lib到IIS
打开VS2013,新建项目,选择WCF|WCF服务库,取名WCFWithIIS 2. 我们用演示一个计算加法的服务.简单起见,直接在IService接口中添加加法接口 3. 实现这个加法 4. 生成项 ...
- go语言合并两个数组
https://stackoverflow.com/questions/16248241/concatenate-two-slices-in-go Add dots after the second ...
- [SaSS] Using Object like style to create class dynamiclly
$black: #; $white: #fff; $yellow: #ffe183; $dark-red: #e70404; $dark-green: #0d8268; $cloud-blue: #d ...
- Codeforces Round #583 (Div. 1 + Div. 2, based on Olympiad of Metropolises) C题
C. Bad Sequence Problem Description: Petya's friends made him a birthday present — a bracket sequenc ...
- Luogu P2151 [SDOI2009]HH去散步 矩乘加速DP
思路:矩乘优化DP 提交:3次(用了一个奇怪的东西导致常数过大) 题解: 如果可以走完正向边后又走反向边那就显然了,但是不能走,所以我们要将正反向边分别编号,区分正反向边. 所以这道题的矩阵是以边的编 ...
- Vue项目开发最新、最全代码规范文档
Vue项目开发最新.最全代码规范文档 2019年02月21日 10:43:49 yw00yw 阅读数 337 一. 目录结构 |— build 构建脚本目录 |— build.js 生产环境构建( ...
- 【luogu1797】faebdc的烦恼-莫队
题目背景 鸟哥(faebdc)自从虐暴NOIP2013以来依然勤奋学习,每天上各种OJ刷题,各种比赛更是不在话下.但这天他遇到了一点小小的麻烦……在一届“Orz鸟哥杯”上,题目是在是太多了!足有n道! ...
- Magic Points ZOJ - 4032
The 15th Zhejiang Provincial Collegiate Programming Contest Sponsored by TuSimple Magic Points ZOJ - ...
- C语言学习笔记4-数据输入和输出
本系列文章由jadeshu编写,转载请注明出处.http://blog.csdn.net/jadeshu/article/details/50752127 作者:jadeshu 邮箱: jades ...
- SpringMVC指定webapp的首页
webapp的首页指的是http://localhost:8080/ 方法一 追加一个[/]URI的请求方法 @Controller public class WelcomeController { ...