spring-security-oauth2 源码分析B路线(二)

前言

在开始之前务必下载好源码 spring-security-5.3.x版本,由于上篇中的问题大量涉及到spring-security,为解释上篇中遗留问题,需要分析spring-security的源码。


案例中的两个AuthenticationManager来自哪里

案例中的3个WebSecurityConfigurerAdapter实现类

在案例中有3个类继承了WebSecurityConfigurerAdapter,分别是:

  1. AuthorizationServerSecurityConfiguration(在DemoAuthorizationServerConfiguration类上的@EnableAuthorizationServer中导入的配置类AuthorizationServerSecurityConfiguration)

  2. ResourceServerConfiguration(在DemoResourceServerConfiguration类上的@EnableResourceServer中导入的配置类ResourceServerConfiguration)

  3. DemoWebSecurityConfiguration

在案例中使用了2个AuthenticationManager

  1. 来自于继承WebSecurityConfigurerAdapter的AuthorizationServerSecurityConfiguration中的某个AuthenticationManagerBuilder对象创建并保存到HttpSecurity对象中的sharedObjects中。

  2. 来自于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基本分析清楚了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值