文章目录
前言
在开始之前务必下载好源码 spring-security-5.3.x版本,由于上篇中的问题大量涉及到spring-security,为解释上篇中遗留问题,需要分析spring-security的源码。
案例中的两个AuthenticationManager来自哪里
案例中的3个WebSecurityConfigurerAdapter实现类
在案例中有3个类继承了WebSecurityConfigurerAdapter,分别是:
-
AuthorizationServerSecurityConfiguration(在DemoAuthorizationServerConfiguration类上的@EnableAuthorizationServer中导入的配置类AuthorizationServerSecurityConfiguration)
-
ResourceServerConfiguration(在DemoResourceServerConfiguration类上的@EnableResourceServer中导入的配置类ResourceServerConfiguration)
-
DemoWebSecurityConfiguration
在案例中使用了2个AuthenticationManager
-
来自于继承WebSecurityConfigurerAdapter的AuthorizationServerSecurityConfiguration中的某个AuthenticationManagerBuilder对象创建并保存到HttpSecurity对象中的sharedObjects中。
-
来自于DemoWebSecurityConfiguration中调用WebSecurityConfigurerAdapter.authenticationManagerBean()方法创建并配置成bean
接下来,以3个继承了WebSecurityConfigurerAdapter的实现类分析AuthenticationManager的来源与其中包含的AuthenticationProvider。
AuthorizationServerSecurityConfiguration视角分析
OK,接下来详细分析为什么是这样。将从WebSecurityConfigurerAdapter开始分析,在其源码中的202行,调用authenticationManager()方法直接返回了一个AuthenticationManager 对象。
AuthenticationManager authenticationManager = authenticationManager();
具体查看该方法:disableLocalConfigureAuthenticationBldr 是否关闭本地配置AuthenticationManagerBuilder,其默认值是false。
protected AuthenticationManager authenticationManager() throws Exception {
if (!authenticationManagerInitialized) {
configure(localConfigureAuthenticationBldr);
/**
* disableLocalConfigureAuthenticationBldr 是否关闭本地配置AuthenticationManagerBuilder,其默认值是false。
* 当调用当前类中的configure(AuthenticationManagerBuilder auth)方法,将该变量设置为true。
*/
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
authenticationManager = localConfigureAuthenticationBldr.build();
}
authenticationManagerInitialized = true;
}
return authenticationManager;
}
当调用当前类中的configure(AuthenticationManagerBuilder auth)方法,将该变量设置为true。
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
this.disableLocalConfigureAuthenticationBldr = true;
}
在AuthorizationServerSecurityConfiguration中,重写的configure(AuthenticationManagerBuilder auth)方法,在源码中是这样的:
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Over-riding to make sure this.disableLocalConfigureAuthenticationBldr = false
// This will ensure that when this configurer builds the AuthenticationManager it will not attempt
// to find another 'Global' AuthenticationManager in the ApplicationContext (if available),
// and set that as the parent of this 'Local' AuthenticationManager.
// This AuthenticationManager should only be wired up with an AuthenticationProvider
// composed of the ClientDetailsService (wired in this configuration) for authenticating 'clients' only.
}
这就是一个空方法,且没有调用父类的实现,即结果是disableLocalConfigureAuthenticationBldr 为false。继续分析,由于localConfigureAuthenticationBldr中没有配置任何AuthenticationProvider,在build的时候返回为null的authenticationManager。(build方法会调用performBuild方法,performBuild方法中调用isConfigured方法且为false的时候直接返回null,isConfigured方法:return !authenticationProviders.isEmpty() || parentAuthenticationManager != null)
if (disableLocalConfigureAuthenticationBldr) {
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
else {
// 由于localConfigureAuthenticationBldr中没有配置任何AuthenticationProvider,在build的时候返回了null
authenticationManager = localConfigureAuthenticationBldr.build();
}
在源码203行,将当前这个authenticationManager保存到authenticationBuilder中作为parentAuthenticationManager。
/**
* 将当前这个authenticationManager保存到authenticationBuilder中作为parentAuthenticationManager
*/
authenticationBuilder.parentAuthenticationManager(authenticationManager);
在源码206行,调用HttpSecurity有参构造方法,在该构造方法中将当前authenticationBuilder保存到了sharedObjects中。
public HttpSecurity(ObjectPostProcessor<Object> objectPostProcessor,
AuthenticationManagerBuilder authenticationBuilder,
Map<Class<?>, Object> sharedObjects) {
super(objectPostProcessor);
Assert.notNull(authenticationBuilder, "authenticationBuilder cannot be null");
setSharedObject(AuthenticationManagerBuilder.class, authenticationBuilder);
在源码218行,使用anonymous()方法,其方法中应用 AnonymousConfigurer ,默认创建了AnonymousAuthenticationProvider。
if (!disableDefaults) {
// @formatter:off
http
.csrf().and()
.addFilter(new WebAsyncManagerIntegrationFilter())
.exceptionHandling().and()
.headers().and()
.sessionManagement().and()
.securityContext().and()
.requestCache().and()
.anonymous().and()
在HttpSecurity类中调用beforeConfigure()方法,使用sharedObjects中保存的AuthenticationManagerBuilder对象调用build方法并保存构造生成的AuthenticationManager对象到sharedObjects中。
@Override
protected void beforeConfigure() throws Exception {
setSharedObject(AuthenticationManager.class, getAuthenticationRegistry().build());
}
private AuthenticationManagerBuilder getAuthenticationRegistry() {
return getSharedObject(AuthenticationManagerBuilder.class);
}
在其源码231行,调用当前类中的configure(HttpSecurity http)方法,该方法被AuthorizationServerSecurityConfiguration重写
configure(http);//调用当前类中的configure(HttpSecurity http)方法,该方法被AuthorizationServerSecurityConfiguration重写
在AuthorizationServerSecurityConfiguration源码第78行,应用了AuthorizationServerSecurityConfigurer
@Override
protected void configure(HttpSecurity http) throws Exception {
AuthorizationServerSecurityConfigurer configurer = new AuthorizationServerSecurityConfigurer();
FrameworkEndpointHandlerMapping handlerMapping = endpoints.oauth2EndpointHandlerMapping();
http.setSharedObject(FrameworkEndpointHandlerMapping.class, handlerMapping);
configure(configurer);
http.apply(configurer); //应用了AuthorizationServerSecurityConfigurer
在AuthorizationServerSecurityConfiguration源码init(HttpSecurity http)中,无论是否使用自定义的passwordEncoder,都会调用AuthenticationManagerBuilder的userDetailsService方法保存ClientDetailsUserDetailsService。
@Override
public void init(HttpSecurity http) throws Exception {
registerDefaultAuthenticationEntryPoint(http);
if (passwordEncoder != null) {
ClientDetailsUserDetailsService clientDetailsUserDetailsService = new ClientDetailsUserDetailsService(clientDetailsService());
clientDetailsUserDetailsService.setPasswordEncoder(passwordEncoder());
http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(clientDetailsUserDetailsService) // 保存clientDetailsUserDetailsService
.passwordEncoder(passwordEncoder());
}
else {
http.userDetailsService(new ClientDetailsUserDetailsService(clientDetailsService())); // 最后调用AuthenticationManagerBuilder中的userDetailsService方法
}
在AuthenticationManagerBuilder源码userDetailsService方法中,应用传入ClientDetailsUserDetailsService的DaoAuthenticationConfigurer
public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
T userDetailsService) throws Exception {
this.defaultUserDetailsService = userDetailsService;
return apply(new DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T>(
userDetailsService));
}
在AbstractDaoAuthenticationConfigurer源码第95行的configure(B builder)方法中调用builder.authenticationProvider(provider),provider即是定义在当前类中的DaoAuthenticationProvider
private DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
@Override
public void configure(B builder) throws Exception {
provider = postProcess(provider);
builder.authenticationProvider(provider);
}
当使用这个保存到sharedObjects中的AuthenticationManagerBuilder最终构建生成了一个包含AnonymousAuthenticationProvider与使用ClientDetailsUserDetailsService的DaoAuthenticationProvider,没有parentAuthenticationManager的AuthenticationManager对象。
我们已经知道了当HttpSecurit.beforeConfigure()方法被调用的会创建AuthenticationManager对象,那么这个方法是什么时候被调用的呢?这个问题留到后面再说。至此以AuthorizationServerSecurityConfiguration对象的视角分析完了AuthenticationManager的来源以及其中包含的AuthenticationProvider。
ResourceServerConfiguration视角分析
根据AuthorizationServerSecurityConfiguration视角分析流程,我们可以加快分析过程。
在WebSecurityConfigurerAdapter其源码中的202行,调用authenticationManager()方法,在authenticationManager()方法中,在其实现类中未重写configure(AuthenticationManagerBuilder auth)方法导致disableLocalConfigureAuthenticationBldr为true,调用了AuthenticationConfiguration配置类中的getAuthenticationManager()方法来创建authenticationManager
if (disableLocalConfigureAuthenticationBldr) { // 在其实现类中未重写configure(AuthenticationManagerBuilder auth)方法导致disableLocalConfigureAuthenticationBldr为true
authenticationManager = authenticationConfiguration
.getAuthenticationManager();
}
在AuthenticationConfiguration源码getAuthenticationManager()方法中,使用了当前类中配置的AuthenticationManagerBuilder的bean来构建AuthenticationManager
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) {
return new AuthenticationManagerDelegator(authBuilder);
}
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
//这里的AuthenticationManagerBuilder对象authBuilder是当前类中配置的AuthenticationManagerBuilder的bean
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
@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;
}
在authBuilder.build()构建authenticationManager对象中,按照其经验,应当调用apply方法中应用了某些配置类,在其中配置了authenticationProvider。
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
在GlobalAuthenticationConfigurerAdapter抽象类的实现类InitializeUserDetailsBeanManagerConfigurer中发现了AuthenticationProvider配置
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
auth.authenticationProvider(provider);
}
在上面的方法中,发现UserDetailsService、PasswordEncoder都是来自于ApplicationContext中获取对应类型的bean,然后以其创建了DaoAuthenticationProvider 并设置到AuthenticationManagerBuilder中。
同样,在WebSecurityConfigurerAdapter源码218行,使用anonymous()方法,其方法中应用 AnonymousConfigurer ,但在ResourceServerConfiguration源码中重写的configure(HttpSecurity http)方法中直接创建并设置了一个key为default的AnonymousAuthenticationProvider,替换了在AnonymousConfigurer 中默认生成的key为UUID随机字符串的AnonymousAuthenticationProvider。这其中是怎么替换的,涉及到更深的源码,之后再分析。
// @formatter:off
http.authenticationProvider(new AnonymousAuthenticationProvider("default"))
// N.B. exceptionHandling is duplicated in resources.configure() so that
当使用这个保存到sharedObjects中的AuthenticationManagerBuilder最终构建生成了一个包含AnonymousAuthenticationProvider与parentAuthenticationManager使用我们配置bean的UserDetailsService的DaoAuthenticationProvider的AuthenticationManager对象。
至此,以ResourceServerConfiguration视角分析AuthenticationManager结束。
DemoWebSecurityConfiguration视角分析
与ResourceServerConfiguration视角分析基本一样,但是使用默认的AnonymousAuthenticationProvider,最终构建生成了一个包含AnonymousAuthenticationProvider与parentAuthenticationManager使用我们配置bean的UserDetailsService的DaoAuthenticationProvider的AuthenticationManager对象,并且在我们的DemoWebSecurityConfiguration配置类中将其配置成了bean。在ResourceServerConfiguration视角分析中最后的AuthenticationManager对象,没有在本案例中进行使用。(言外之意是在其他时候会被使用到)
WebSecurityConfigurerAdapter没有提供public方法获取authenticationBuilder对象,我们也不需要通过authenticationBuilder对象build构建对象,这一步spring-security已经帮我们处理过了,我们主要是需要调用authenticationBuilder.getObject()方法来获取已经构建好的AuthenticationManager对象,spring-security在WebSecurityConfigurerAdapter定义了一个AuthenticationManager接口的实现类AuthenticationManagerDelegator代理来调用AuthenticationManager对象,避免外部破坏结构。
@Bean
@Override
@SneakyThrows
public AuthenticationManager authenticationManagerBean() {
return super.authenticationManagerBean();
}
案例中使用到的两个AuthenticationManager在哪里被调用
ClientCredentialsTokenEndpointFilter使用的是从HttpSecurity#shareObjects中取出的AuthenticationManager对象,并配置在AuthorizationServerSecurityConfigurer#clientCredentialsTokenEndpointFilter(HttpSecurity http)中。很明显这里的AuthenticationManager对象是从AuthorizationServerSecurityConfiguration配置类中的sharedObjects中获取的。
private ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter(HttpSecurity http) {
ClientCredentialsTokenEndpointFilter clientCredentialsTokenEndpointFilter = new ClientCredentialsTokenEndpointFilter(
frameworkEndpointHandlerMapping().getServletPath("/oauth/token"));
clientCredentialsTokenEndpointFilter
.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
BasicAuthenticationFilter使用的是从HttpSecurity#shareObjects中取出的AuthenticationManager对象,并配置在HttpBasicConfigurer.configure(B http)中,在AuthorizationServerSecurityConfiguration重写的configure(HttpSecurity http)方法中去掉父类实现,并在AuthorizationServerSecurityConfigurer的init(HttpSecurity http)方法中调用了HttpSecurity对象的httpBasic()方法,httpBasic()方法应用了HttpBasicConfigurer,最终这里的AuthenticationManager对象也是从AuthorizationServerSecurityConfiguration配置类中的sharedObjects中获取的。
@Override
public void configure(B http) {
AuthenticationManager authenticationManager = http
.getSharedObject(AuthenticationManager.class);
BasicAuthenticationFilter basicAuthenticationFilter = new BasicAuthenticationFilter(
authenticationManager, this.authenticationEntryPoint);
在上篇提到密码授权模式中使用的ResourceOwnerPasswordTokenGranter,而在其中AuthenticationManager就是我们配置成bean的AuthenticationManager对象传入AuthorizationServerEndpointsConfigurer中,最终传入ResourceOwnerPasswordTokenGranter中。
private List<TokenGranter> getDefaultTokenGranters() {
ClientDetailsService clientDetails = clientDetailsService();
AuthorizationServerTokenServices tokenServices = tokenServices();
AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();
OAuth2RequestFactory requestFactory = requestFactory();
List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();
tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,
requestFactory));
tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));
ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);
tokenGranters.add(implicit);
tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));
if (authenticationManager != null) {
tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,
clientDetails, requestFactory));
}
return tokenGranters;
}
案例中两个AuthenticationManager是如何切换认证的
在AuthenticationManager实现类ProviderManager源码authenticate(Authentication authentication)方法中,遍历所有AuthenticationProvider ,使用支持该authentication具体类型的AuthenticationProvider进行认证。而这里的AuthenticationManager对象是一个包含AnonymousAuthenticationProvider,parentAuthenticationManager使用我们配置bean的UserDetailsService的DaoAuthenticationProvider的AuthenticationManager。而AuthenticationManager对象找不到匹配的AuthenticationProvider对象,会尝试使用parentAuthenticationManager进行重试,进而使用我们配置bean的UserDetailsService的DaoAuthenticationProvider进行认证。
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
if (result == null && parent != null) {
// Allow the parent to try.
try {
result = parentResult = parent.authenticate(authentication);
}
总结
spring-security-oauth2与spring-security中使用了大量的设计模式,代理模式、建造者模式、模板方法模式、适配器模式等,不熟悉的话对阅读源码会有一定的障碍。至此,本篇将案例中两个AuthenticationManager基本分析清楚了。