SpringSecurity - 启动流程分析(六)


活动地址:CSDN21天学习挑战赛

前言

在上篇文章 SpringSecurity - 启动流程分析(五) 中,我们知道具体的认证逻辑是交给了 AuthenticationManager 来处理的,查看实现类可以看到只有一个 ProviderManager 实现类(其他都是内部类):

public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {

	private static final Log logger = LogFactory.getLog(ProviderManager.class);

	private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
	private List<AuthenticationProvider> providers = Collections.emptyList();
	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
	private AuthenticationManager parent;
	private boolean eraseCredentialsAfterAuthentication = true;

	public ProviderManager(AuthenticationProvider... providers) {
		this(Arrays.asList(providers), null);
	}

	public ProviderManager(List<AuthenticationProvider> providers) {
		this(providers, null);
	}

	public ProviderManager(List<AuthenticationProvider> providers,
			AuthenticationManager parent) {
		Assert.notNull(providers, "providers list cannot be null");
		this.providers = providers;
		this.parent = parent;
		checkState();
	}
	
	public void afterPropertiesSet() {
		checkState();
	}

	private void checkState() {
		if (parent == null && providers.isEmpty()) {
			throw new IllegalArgumentException(
					"A parent AuthenticationManager or a list "
							+ "of AuthenticationProviders is required");
		} else if (providers.contains(null)) {
			throw new IllegalArgumentException(
					"providers list cannot contain null values");
		}
	}

	// 核心认证方法
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
		...
	}

	@SuppressWarnings("deprecation")
	private void prepareException(AuthenticationException ex, Authentication auth) {
		eventPublisher.publishAuthenticationFailure(ex, auth);
	}

	private void copyDetails(Authentication source, Authentication dest) {
		if ((dest instanceof AbstractAuthenticationToken) && (dest.getDetails() == null)) {
			AbstractAuthenticationToken token = (AbstractAuthenticationToken) dest;

			token.setDetails(source.getDetails());
		}
	}

	public List<AuthenticationProvider> getProviders() {
		return providers;
	}

	public void setMessageSource(MessageSource messageSource) {
		this.messages = new MessageSourceAccessor(messageSource);
	}

	public void setAuthenticationEventPublisher(
			AuthenticationEventPublisher eventPublisher) {
		Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
		this.eventPublisher = eventPublisher;
	}

	public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
		this.eraseCredentialsAfterAuthentication = eraseSecretData;
	}

	public boolean isEraseCredentialsAfterAuthentication() {
		return eraseCredentialsAfterAuthentication;
	}

	private static final class NullEventPublisher implements AuthenticationEventPublisher {
		public void publishAuthenticationFailure(AuthenticationException exception,
				Authentication authentication) {
		}

		public void publishAuthenticationSuccess(Authentication authentication) {
		}
	}
}

我们大致看一下 ProviderManager 源码,可以看到其中一个变量:

private List<AuthenticationProvider> providers = Collections.emptyList();

这个变量存储了需要认证的 provider

接着查看一下核心方法:authenticate()

public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
	Class<? extends Authentication> toTest = authentication.getClass();
	AuthenticationException lastException = null;
	AuthenticationException parentException = null;
	Authentication result = null;
	Authentication parentResult = null;
	boolean debug = logger.isDebugEnabled();

	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;
			}
		}
		catch (AccountStatusException | InternalAuthenticationServiceException e) {
			prepareException(e, authentication);
			// SEC-546: Avoid polling additional providers if auth failure is due to
			// invalid account status
			throw e;
		} catch (AuthenticationException e) {
			lastException = e;
		}
	}

	if (result == null && parent != null) {
		// Allow the parent to try.
		try {
			result = parentResult = parent.authenticate(authentication);
		}
		catch (ProviderNotFoundException e) {
			// ignore as we will throw below if no other exception occurred prior to
			// calling parent and the parent
			// may throw ProviderNotFound even though a provider in the child already
			// handled the request
		}
		catch (AuthenticationException e) {
			lastException = parentException = e;
		}
	}

	if (result != null) {
		if (eraseCredentialsAfterAuthentication
				&& (result instanceof CredentialsContainer)) {
			// Authentication is complete. Remove credentials and other secret data
			// from authentication
			((CredentialsContainer) result).eraseCredentials();
		}

		// If the parent AuthenticationManager was attempted and successful then it will publish an AuthenticationSuccessEvent
		// This check prevents a duplicate AuthenticationSuccessEvent if the parent AuthenticationManager already published it
		if (parentResult == null) {
			eventPublisher.publishAuthenticationSuccess(result);
		}
		return result;
	}

	// Parent was null, or didn't authenticate (or throw an exception).

	if (lastException == null) {
		lastException = new ProviderNotFoundException(messages.getMessage(
				"ProviderManager.providerNotFound",
				new Object[] { toTest.getName() },
				"No AuthenticationProvider found for {0}"));
	}

	// If the parent AuthenticationManager was attempted and failed then it will publish an AbstractAuthenticationFailureEvent
	// This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it
	if (parentException == null) {
		prepareException(lastException, authentication);
	}

	throw lastException;
}

可以看到,authenticate() 方法中循环调用了 List<AuthenticationProvider> 中的 authenticate() 方法。

到这里为止我们知道了认证是交给了各种 AuthenticationProvider 的组合来处理的。查看 AuthenticationProvider 的实现类:

在这里插入图片描述

分析

接下来我们分析一下 SpringSecurity 是什么时候初始化 AuthenticationManagerProviderManager

SpringSecurity - 启动流程分析(二) 这篇文章中,我们知道默认的过滤器链是通过调用 WebSecurityConfigurerAdaptergetHttp() 方法获取到 HttpSecurity,只有由 HttpSecurity 生成过滤器链。我们来查看以下 getHttp() 方法:

protected final HttpSecurity getHttp() throws Exception {
	...
	// 核心方法,初始化 AuthenticationManager
	AuthenticationManager authenticationManager = authenticationManager();
	authenticationBuilder.parentAuthenticationManager(authenticationManager);
	Map<Class<?>, Object> sharedObjects = createSharedObjects();

	http = new HttpSecurity(objectPostProcessor, authenticationBuilder,
			sharedObjects);
	...
	return http;
}

查看 authenticationManager() 方法:

protected AuthenticationManager authenticationManager() throws Exception {
	if (!authenticationManagerInitialized) {
		configure(localConfigureAuthenticationBldr);
		if (disableLocalConfigureAuthenticationBldr) {
			// 核心流程
			authenticationManager = authenticationConfiguration
					.getAuthenticationManager();
		}
		else {
			authenticationManager = localConfigureAuthenticationBldr.build();
		}
		authenticationManagerInitialized = true;
	}
	return authenticationManager;
}

查看 AuthenticationConfigurationgetAuthenticationManager() 方法:

// 添加 GlobalAuthenticationConfigurerAdapter 类型的 Bean 到 IoC容器 中
@Bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
		ApplicationContext context) {
	return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
// 添加 GlobalAuthenticationConfigurerAdapter 类型的 Bean 到 IoC容器 中
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
	return new InitializeUserDetailsBeanManagerConfigurer(context);
}
// 添加 GlobalAuthenticationConfigurerAdapter 类型的 Bean 到 IoC容器 中
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
	return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
	
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(
		List<GlobalAuthenticationConfigurerAdapter> configurers) {
	configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
	this.globalAuthConfigurers = configurers;
}

@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
		ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
	// 初始化默认的 PasswordEncoder,跟踪源码可知是 DelegatingPasswordEncoder
	LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
	AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);

	// 设置 AuthenticationManagerBuilder
	DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
	if (authenticationEventPublisher != null) {
		result.authenticationEventPublisher(authenticationEventPublisher);
	}
	return result;
}

public AuthenticationManager getAuthenticationManager() throws Exception {
	if (this.authenticationManagerInitialized) {
		return this.authenticationManager;
	}
	// 这里添加了默认的内部类 DefaultPasswordEncoderAuthenticationManagerBuilder 到 IoC容器 中
	AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
	if (this.buildingAuthenticationManager.getAndSet(true)) {
		return new AuthenticationManagerDelegator(authBuilder);
	}
	// 在 setGlobalAuthenticationConfigurers 中自动注入并排序
	for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
		// 具体的逻辑,跟踪源码可以看到里面初始化了 DaoAuthenticationProvider
		authBuilder.apply(config);
	}

	// 初始化完成 AuthenticationManager
	authenticationManager = authBuilder.build();

	if (authenticationManager == null) {
		authenticationManager = getAuthenticationManagerBean();
	}

	this.authenticationManagerInitialized = true;
	return authenticationManager;
}

通过以上源码跟踪,我们知道了默认会初始化一个 DaoAuthenticationProvider 作为 ProviderManager 中的 List<AuthenticationProvider> 属性的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值