ProviderManager
是Spring Security
提供的AuthenticationManager
实现。其主要目的,也就是实现AuthenticationManager
接口所定义的方法
Authentication authenticate(Authentication authentication) throws AuthenticationException
ProviderManager
使用一组AuthenticationProvider
,也可以再附加一个双亲认证管理器AuthenticationManager
来完成对一个认证请求,也就是一个认证令牌对象authentication
的认证。
ProviderManager
的认证过程也会发布相应的认证成功/异常事件。
ProviderManager
的认证逻辑会遍历所有支持该认证令牌对象参数 authentication
(基于类型进行匹配)的 AuthenticationProvider
,找到第一个能成功认证的并返回填充更多信息的authentication
对象:
AuthenticationProvider
宣称可以认证该 authentication
,但是认证过程抛出异常 AuthenticationException
,则整个认证过程不会停止, 而是尝试使用下一个 AuthenticationProvider
继续;AuthenticationProvider
宣称可以认证该 authentication
,但是认证过程抛出异常 AccountStatusException
/InternalAuthenticationServiceException
, 则异常会被继续抛出,整个认证过程停止;AuthenticationProvider
宣称可以认证该 authentication
,并且成功认证该 authentication
,则认证过程停止,该结果会被采用。如果所有的 AuthenticationProvider
尝试完之后也未能认证该 authentication
,并且双亲认证管理器被设置,则该方法会继续尝试使用双亲认证管理器认证该 authentication
。
如果所有的 AuthenticationProvider
都尝试过,并且双亲认证管理器也未能认证该 authentication
,则会抛出异常 ProviderNotFoundException
。
认证成功时,如果设置了标志需要擦除认证中的凭证信息,则该方法会擦除认证中的凭证信息。
认证成功时,该方法也会调用 eventPublisher
发布认证成功事件。
认证异常时,该方法回调用 eventPublisher
发布相应的认证异常事件。
源代码版本 : Spring Security Config 5.1.4.RELEASE
package org.springframework.security.authentication;
import java.util.Collections;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.CredentialsContainer;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.util.Assert;
/**
* Iterates an Authentication request through a list of AuthenticationProviders.
*
*
* AuthenticationProviders are usually tried in order until one provides a
* non-null response. A non-null response indicates the provider had authority to decide
* on the authentication request and no further providers are tried. If a subsequent
* provider successfully authenticates the request, the earlier authentication exception
* is disregarded and the successful authentication will be used. If no subsequent
* provider provides a non-null response, or a new AuthenticationException,
* the last AuthenticationException received will be used. If no provider
* returns a non-null response, or indicates it can even process an
* Authentication, the ProviderManager will throw a
* ProviderNotFoundException. A parent AuthenticationManager can also
* be set, and this will also be tried if none of the configured providers can perform the
* authentication. This is intended to support namespace configuration options though and
* is not a feature that should normally be required.
*
* The exception to this process is when a provider throws an
* AccountStatusException, in which case no further providers in the list will be
* queried.
*
* Post-authentication, the credentials will be cleared from the returned
* Authentication object, if it implements the CredentialsContainer
* interface. This behaviour can be controlled by modifying the
* #setEraseCredentialsAfterAuthentication(boolean)
* eraseCredentialsAfterAuthentication property.
*
* Event Publishing
*
* Authentication event publishing is delegated to the configured
* AuthenticationEventPublisher which defaults to a null implementation which
* doesn't publish events, so if you are configuring the bean yourself you must inject a
* publisher bean if you want to receive events. The standard implementation is
* DefaultAuthenticationEventPublisher which maps common exceptions to events (in
* the case of authentication failure) and publishes an
* org.springframework.security.authentication.event.AuthenticationSuccessEvent
* AuthenticationSuccessEvent} if authentication succeeds. If you are using the namespace
* then an instance of this bean will be used automatically by the "http"
* configuration, so you will receive events from the web part of your application
* automatically.
*
* Note that the implementation also publishes authentication failure events when it
* obtains an authentication result (or an exception) from the "parent"
* AuthenticationManager if one has been set. So in this situation, the parent
* should not generally be configured to publish events or there will be duplicates.
*
*
* @author Ben Alex
* @author Luke Taylor
*
* @see DefaultAuthenticationEventPublisher
*/
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// ~ Static fields/initializers
// =======================================================
private static final Log logger = LogFactory.getLog(ProviderManager.class);
// ~ Instance fields
// =======================================================
// 认证事件发布器,这里缺省初始化为 NullEventPublisher,表示不做认证事件的发布
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
// 用于记录所要使用的各个 AuthenticationProvider, 当前 ProviderManager 的认证
// 任务最终委托给这组 AuthenticationProvider 完成
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 双亲认证管理器,可以设置,也可以不设置,设置的话会在当前认证管理器 ProviderManager
// 不能认证某个用户时再尝试使用该双亲认证管理器认证用户
private AuthenticationManager parent;
// 认证成功时是否擦除认证令牌对象中的凭证信息(比如密码),缺省值为 true
private boolean eraseCredentialsAfterAuthentication = true;
// 构造函数,指定一组要使用的 AuthenticationProvider,并且双亲认证管理器设置为 null
public ProviderManager(List<AuthenticationProvider> providers) {
this(providers, null);
}
// 构造函数,指定一组要使用的 AuthenticationProvider,并且双亲认证管理器设置为指定值
public ProviderManager(List<AuthenticationProvider> providers,
AuthenticationManager parent) {
Assert.notNull(providers, "providers list cannot be null");
this.providers = providers;
this.parent = parent;
checkState();
}
// ~ Methods
// ======================================================
// InitializingBean 接口定义的bean初始化方法,会在该bean创建过程中初始化阶段被调用,
// 这里的实现仅仅检查必要的工作组件是否被设置,如果没有被设置,则抛出异常
// IllegalArgumentException
public void afterPropertiesSet() throws Exception {
checkState();
}
private void checkState() {
if (parent == null && providers.isEmpty()) {
throw new IllegalArgumentException(
"A parent AuthenticationManager or a list "
+ "of AuthenticationProviders is required");
}
}
// 尝试对认证请求对象,也就是认证令牌对象参数 authentication 进行认证
// 该方法的逻辑会遍历所有支持该认证令牌对象参数 authentication (基于类型进行匹配)
// 的 AuthenticationProvider,找到第一个能成功认证的并返回填充更多信息的
// authentication 对象:
// 1. 如果某个 AuthenticationProvider 宣称可以认证该 authentication,
// 但是认证过程抛出异常 AuthenticationException,则整个认证过程不会停止,
// 而是尝试使用下一个 AuthenticationProvider 继续;
// 2. 如果某个 AuthenticationProvider 宣称可以认证该 authentication,
// 但是认证过程抛出异常 AccountStatusException/InternalAuthenticationServiceException,
// 则异常会被继续抛出,整个认证过程停止;
// 3. 如果某个 AuthenticationProvider 宣称可以认证该 authentication,并且成功认证
// 该 authentication,则认证过程停止,该结果会被采用。
// 如果所有的 AuthenticationProvider 尝试完之后也未能认证该 authentication,
// 并且双亲认证管理器被设置,则该方法会继续尝试使用双亲认证管理器认证该 authentication。
// 如果所有的 AuthenticationProvider 都尝试过,并且双亲认证管理器也未能认证
// 该 authentication,则会抛出异常 ProviderNotFoundException
// 认证成功时,如果设置了标志需要擦除认证中的凭证信息,则该方法会擦除认证中的
// 凭证信息。
// 认证成功时,该方法也会调用 eventPublisher 发布认证成功事件。
// 认证异常时,该方法回调用 eventPublisher 发布相应的认证异常事件。
/**
* Attempts to authenticate the passed Authentication object.
*
* The list of AuthenticationProviders will be successively tried until an
* AuthenticationProvider indicates it is capable of authenticating the
* type of Authentication object passed. Authentication will then be
* attempted with that AuthenticationProvider.
*
* If more than one AuthenticationProvider supports the passed
* Authentication object, the first one able to successfully
* authenticate the Authentication object determines the
* result, overriding any possible AuthenticationException
* thrown by earlier supporting AuthenticationProviders.
* On successful authentication, no subsequent AuthenticationProviders
* will be tried.
* If authentication was not successful by any supporting
* AuthenticationProvider the last thrown
* AuthenticationException will be rethrown.
*
* @param authentication the authentication request object.
*
* @return a fully authenticated object including credentials.
*
* @throws AuthenticationException if authentication fails.
*/
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 e) {
prepareException(e, authentication);
// SEC-546: Avoid polling additional providers if auth failure is due to
// invalid account status
throw e;
}
catch (InternalAuthenticationServiceException e) {
prepareException(e, authentication);
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 than 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 than 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;
}
// 发布认证异常事件
@SuppressWarnings("deprecation")
private void prepareException(AuthenticationException ex, Authentication auth) {
eventPublisher.publishAuthenticationFailure(ex, auth);
}
/**
* Copies the authentication details from a source Authentication object to a
* destination one, provided the latter does not already have one set.
*
* @param source source authentication
* @param dest the destination authentication object
*/
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);
}
// 指定事件发布器,用于覆盖缺省的 NullEventPublisher
public void setAuthenticationEventPublisher(
AuthenticationEventPublisher eventPublisher) {
Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null");
this.eventPublisher = eventPublisher;
}
/**
* If set to, a resulting Authentication which implements the
* CredentialsContainer interface will have its
* CredentialsContainer#eraseCredentials() eraseCredentials method called
* before it is returned from the authenticate() method.
*
* @param eraseSecretData set to false to retain the credentials data in
* memory. Defaults to true.
*/
public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) {
this.eraseCredentialsAfterAuthentication = eraseSecretData;
}
public boolean isEraseCredentialsAfterAuthentication() {
return eraseCredentialsAfterAuthentication;
}
// 这是一个缺省使用的认证事件发布器实现类,实际上并不发布任何认证事件,只是为了避免
// ProviderManager 的属性 eventPublisher 为 null
private static final class NullEventPublisher implements AuthenticationEventPublisher {
public void publishAuthenticationFailure(AuthenticationException exception,
Authentication authentication) {
}
public void publishAuthenticationSuccess(Authentication authentication) {
}
}
}
Spring Security : 概念模型 AuthenticationManager 认证管理器