1,簡要說明
在Spring Security中對用戶進行認證的是AuthenticationManager,其只有一個方法,嘗試對封裝了認證信息的Authentication進行身份驗證,如果成功,則返回完全填充的Authentication(包括授予的權限)。
public interface AuthenticationManager {
/**
* 嘗試對通過Authentication實例對象封裝的身份信息進行驗證。
* 如果驗證成功,則返回完全填充的Authentication對象(包括授予的權限)。
*
* AuthenticationManager 建議遵循以下的約定
* 1,如果帳戶被禁用并且AuthenticationManager可以測試此狀態,則必須引發 DisabledException
* 2,如果帳戶被鎖定并且并且AuthenticationManager可以測試帳戶鎖定,則必須拋出LockedException
* 3,如果憑據不正確,則必須拋出BadCredentialsException
* 雖然上述選項是可選的,但是 AuthenticationManager 必須始終測試憑據。
* 我們應該上述順序捕獲這些異常,同時實現者也應按上述順序拋出異常(即,如果帳戶被禁用或鎖定,
* 則立即拒絕身份驗證請求,并且不執行憑據測試過程),這可以防止根據禁用或鎖定的帳戶測試憑據。
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
}
AuthenticationManager 只關注認證成功與否而并不關心具體的認證方式。例如我們可以通過用戶名及密碼、短信、刷臉、OAuth2協議等方式進行認證。對于這些具體認證方式是交給了AuthenticationProvider來負責。
public interface AuthenticationProvider {
/**
*使用與AuthenticationManager的authenticate方法相同的
*協定執行身份驗證(例如按照什么規則拋出異常等)
*/
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
/**
*如果支持指定的Authentication 對象,則返回true</code
*/
boolean supports(Class<?> authentication);
}
下面展示了AuthenticationProvider的部分實現
AuthenticationManager與AuthenticationProvider 是怎么被創建使用,以及如何自由的添加認證方式的問題,在下面的內容中將進一步分析
2,認證方式的全局配置
前面章節分析了WebSecurityConfiguration配置類里面相關的內容,現在回到@EnableWebSecurity 注解上,我們接著分析下@EnableGlobalAuthentication內部都做了些啥
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import({ WebSecurityConfiguration.class,
SpringWebMvcImportSelector.class,
OAuth2ImportSelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableWebSecurity {
/**
* Controls debugging support for Spring Security. Default is false.
* @return if true, enables debug support with Spring Security
*/
boolean debug() default false;
}
@EnableGlobalAuthentication
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(value = { java.lang.annotation.ElementType.TYPE })
@Documented
@Import(AuthenticationConfiguration.class)
@Configuration
public @interface EnableGlobalAuthentication {
}
其重點就是導入了AuthenticationConfiguration配置對象
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
... ...
}
AuthenticationConfiguration 中還導入了ObjectPostProcessorConfiguration配置,該配置比較簡單,就是實例化了一個bean,而該Bean在前面的章節中也在不斷的用到
@Configuration(proxyBeanMethods = false)
public class ObjectPostProcessorConfiguration {
@Bean
public ObjectPostProcessor<Object> objectPostProcessor(
AutowireCapableBeanFactory beanFactory) {
return new AutowireBeanFactoryObjectPostProcessor(beanFactory);
}
}
3,AuthenticationConfiguration
下面,我們深入分析下AuthenticationConfiguration配置類的實現。
先簡單的說下AuthenticationManager構建的主體過程
- AuthenticationConfiguration中收集所有GlobalAuthenticationConfigurerAdapter類型的Bean并保存到globalAuthConfigurers中;
- AuthenticationConfiguration中創建AuthenticationManagerBuilder類型的實例對象DefaultPasswordEncoderAuthenticationManagerBuilder;
- 將globalAuthConfigurers里面的對象傳遞給AuthenticationManagerBuilder;
- 執行AuthenticationManagerBuilder的build()方法完成AuthenticationManager的構建;
- WebSecurityConfigurerAdapter中調用AuthenticationConfiguration的getAuthenticationManager()方法得到構建的AuthenticationManager類似對象;
@Configuration(proxyBeanMethods = false)
@Import(ObjectPostProcessorConfiguration.class)
public class AuthenticationConfiguration {
private AtomicBoolean buildingAuthenticationManager = new AtomicBoolean();
private ApplicationContext applicationContext;
private AuthenticationManager authenticationManager;
private boolean authenticationManagerInitialized;
private List<GlobalAuthenticationConfigurerAdapter> globalAuthConfigurers = Collections
.emptyList();
private ObjectPostProcessor<Object> objectPostProcessor;
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Autowired
public void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
this.objectPostProcessor = objectPostProcessor;
}
//收集GlobalAuthenticationConfigurerAdapter類型對象
@Autowired(required = false)
public void setGlobalAuthenticationConfigurers(
List<GlobalAuthenticationConfigurerAdapter> configurers) {
configurers.sort(AnnotationAwareOrderComparator.INSTANCE);
this.globalAuthConfigurers = configurers;
}
@Bean
public static GlobalAuthenticationConfigurerAdapter enableGlobalAuthenticationAutowiredConfigurer(
ApplicationContext context) {
//GlobalAuthenticationConfigurerAdapter類型對象
return new EnableGlobalAuthenticationAutowiredConfigurer(context);
}
//目的是構建默認的DaoAuthenticationProvider
@Bean
public static InitializeUserDetailsBeanManagerConfigurer initializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
//GlobalAuthenticationConfigurerAdapter類型對象
return new InitializeUserDetailsBeanManagerConfigurer(context);
}
//目的是將ApplicationContext中存在的AuthenticationProvider類型直接添加進來
@Bean
public static InitializeAuthenticationProviderBeanManagerConfigurer initializeAuthenticationProviderBeanManagerConfigurer(ApplicationContext context) {
//GlobalAuthenticationConfigurerAdapter類型對象
return new InitializeAuthenticationProviderBeanManagerConfigurer(context);
}
//身份驗證管理器生成器【下面個方法會獲取使用】
@Bean
public AuthenticationManagerBuilder authenticationManagerBuilder(
ObjectPostProcessor<Object> objectPostProcessor, ApplicationContext context) {
//創建“惰性”密碼編碼器,所謂“惰性”是指在真正使用到某個方法時才去到ApplicationContext中
//獲取PasswordEncoder類型的對象進行調用【對于不確定某對象何時加入到ApplicationContext是挺有用】
//默認情況,如果ApplicationContext沒有得到PasswordEncoder類型的實例,
//則使用默認的DelegatingPasswordEncoder
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
//從ApplicationContext中獲取AuthenticationEventPublisher類型的bean
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context, AuthenticationEventPublisher.class);
//創建默認的AuthenticationManagerBuilder實例
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(objectPostProcessor, defaultPasswordEncoder);
if (authenticationEventPublisher != null) {
result.authenticationEventPublisher(authenticationEventPublisher);
}
return result;
}
//創建身份認證管理器實例
//注意:該方法在WebSecurityConfigurerAdapter中被調用
public AuthenticationManager getAuthenticationManager() throws Exception {
if (this.authenticationManagerInitialized) {
return this.authenticationManager;
}
//獲取身份驗證管理器生成器DefaultPasswordEncoderAuthenticationManagerBuilder
AuthenticationManagerBuilder authBuilder = this.applicationContext.getBean(AuthenticationManagerBuilder.class);
if (this.buildingAuthenticationManager.getAndSet(true)) { //第一次調用不會進入,后面都會進入
return new AuthenticationManagerDelegator(authBuilder);
}
for (GlobalAuthenticationConfigurerAdapter config : globalAuthConfigurers) {
authBuilder.apply(config);
}
//重點需要關注這個方法執行的過程【重點】
authenticationManager = authBuilder.build();
if (authenticationManager == null) {
authenticationManager = getAuthenticationManagerBean();
}
this.authenticationManagerInitialized = true;
return authenticationManager;
}
private AuthenticationManager getAuthenticationManagerBean() {
return lazyBean(AuthenticationManager.class);
}
}
3.1 ,DefaultPasswordEncoderAuthenticationManagerBuilder
由上門可知,其默認使用DefaultPasswordEncoderAuthenticationManagerBuilder作為認證管理的構建器,下面分析其build()方法的執行過程。
DefaultPasswordEncoderAuthenticationManagerBuilder在執行build()方法時,其父類AbstractConfiguredSecurityBuilder的doBuild()方法被執行,前面有說過,這個方法是個模板方法,如下所示:
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
private final Log logger = LogFactory.getLog(getClass());
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
private final List<SecurityConfigurer<O, B>> configurersAddedInInitializing = new ArrayList<>();
private final Map<Class<?>, Object> sharedObjects = new HashMap<>();
private final boolean allowConfigurersOfSameType;
private BuildState buildState = BuildState.UNBUILT;
private ObjectPostProcessor<Object> objectPostProcessor;
//模板方法
protected final O doBuild() throws Exception {
synchronized (configurers) {
buildState = BuildState.INITIALIZING;
beforeInit();
init(); //這個方法的執行是關鍵
buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
buildState = BuildState.BUILDING;
O result = performBuild(); //執行子類的具體實現
buildState = BuildState.BUILT;
return result;
}
}
protected abstract O performBuild() throws Exception;
@SuppressWarnings("unchecked")
private void init() throws Exception {
//當前這里存儲的是GlobalAuthenticationConfigurerAdapter類型的實例,
//默認的有:InitializeUserDetailsBeanManagerConfigurer、
//InitializeAuthenticationProviderBeanManagerConfigurer、
//EnableGlobalAuthenticationAutowiredConfigurer
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : configurersAddedInInitializing) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
//當前這里存儲的是GlobalAuthenticationConfigurerAdapter類型的實例,
//默認的有:InitializeUserDetailsBeanManagerConfigurer、
//InitializeAuthenticationProviderBeanManagerConfigurer、
//EnableGlobalAuthenticationAutowiredConfigurer
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
}
接著我們分析下這三個默認的GlobalAuthenticationConfigurerAdapter類型的實例中init和configure方法都做了啥
3.2 InitializeUserDetailsBeanManagerConfigurer
該類的目的純粹是為了添加InitializeUserDetailsManagerConfigurer配置,通過在其configure方法階段創建DaoAuthenticationProvider對象,最終被添加到ProviderManager中
@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer
extends GlobalAuthenticationConfigurerAdapter {
static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;
private final ApplicationContext context;
/**
* @param context
*/
InitializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
this.context = context;
}
//注意:這里并沒有重寫configure方法
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
//直接添加InitializeUserDetailsManagerConfigurer配置類
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer
extends GlobalAuthenticationConfigurerAdapter {
//重寫configure方法
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
if (auth.isConfigured()) {
return;
}
//用于根據用戶名得到用戶信息
//Springboot的自動化配置中會默認創建InMemoryUserDetailsManager
UserDetailsService userDetailsService = getBeanOrNull(
UserDetailsService.class);
if (userDetailsService == null) {
return;
}
//用于對用戶的密碼進行加密以及密碼比對驗證
PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
//用戶更新用戶密碼
UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
if (passwordEncoder != null) {
provider.setPasswordEncoder(passwordEncoder);
}
if (passwordManager != null) {
provider.setUserDetailsPasswordService(passwordManager);
}
//用于驗證provider中的userDetailsService是否為空【不許為空】
provider.afterPropertiesSet();
//直接添加回DefaultPasswordEncoderAuthenticationManagerBuilder
auth.authenticationProvider(provider);
}
/**
* @return a bean of the requested class if there's just a single registered component, null otherwise.
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] userDetailsBeanNames = InitializeUserDetailsBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
if (userDetailsBeanNames.length != 1) {
return null;
}
return InitializeUserDetailsBeanManagerConfigurer.this.context
.getBean(userDetailsBeanNames[0], type);
}
}
}
Springboot的自動化配置中會默認創建InMemoryUserDetailsManager,請參考Spring Security解析二:自動化裝配
我們也可以通過配置來指定,例如:
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(dataSource)
.withDefaultSchema()
.withUser("user").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}
接著進一步研究下DaoAuthenticationProvider都做了些啥,它是怎么對身份進行認證的?
3.2.1 AbstractUserDetailsAuthenticationProvider
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
// ~ Instance fields
// ================================================================================================
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private UserCache userCache = new NullUserCache();
private boolean forcePrincipalAsString = false;
protected boolean hideUserNotFoundExceptions = true;
private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
//說明該身份認證方式僅適合UsernamePasswordAuthenticationToken
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
@Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// 從認證主體中得到用戶名(或返回未提供-NONE_PROVIDED)
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
//從緩存中取得用戶信息(默認使用的緩存是NullUserCache)
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//檢索用戶【由子類實現】
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (UsernameNotFoundException notFound) {
logger.debug("User '" + username + "' not found");
if (hideUserNotFoundExceptions) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
else {
throw notFound;
}
}
Assert.notNull(user,
"retrieveUser returned null - a violation of the interface contract");
}
try {
//檢測UserDetails的狀態【是否被鎖、是否可用、是否過期】
preAuthenticationChecks.check(user);
//子類執行任何附加檢查,例如:驗證密碼是否匹配
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
catch (AuthenticationException exception) {
if (cacheWasUsed) {
//如果從緩存中得到的用戶信息驗證失敗,則從非緩存的地方得到重新驗證
// There was a problem, so try again after checking
// we're using latest data (i.e. not from the cache)
cacheWasUsed = false;
user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
preAuthenticationChecks.check(user);
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
}
else {
throw exception;
}
}
//檢測UserDetails的狀態【憑據是否未過期】
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
//將通過檢查的用戶信息加入緩存
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) { //值返回用戶名
principalToReturn = user.getUsername();
}
//返回成功的響應
return createSuccessAuthentication(principalToReturn, authentication, user);
}
//注意:子類有重寫
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// Ensure we return the original credentials the user supplied,
// so subsequent attempts are successful even with encoded passwords.
// Also ensure we return the original getDetails(), so that future
// authentication events after cache expiry contain the details
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
}
可見上的操作主要是從某個地方得到用戶信息,然后檢查用戶的狀態,如果檢查失敗則拋出相應的異常,否則返回成功的認證信息。
上面的retrieveUser與additionalAuthenticationChecks是需要繼續研究的地方
3.2.2 DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private PasswordEncoder passwordEncoder;
private UserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@SuppressWarnings("deprecation")
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
@Override
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
catch (UsernameNotFoundException ex) {
mitigateAgainstTimingAttack(authentication);
throw ex;
}
catch (InternalAuthenticationServiceException ex) {
throw ex;
}
catch (Exception ex) {
throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
}
}
@Override
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
}
上面通過userDetailsService來得到用戶的信息,并通過passwordEncoder來驗證密碼是否正確,而這兩個對象是通過上面 3.2小結里的InitializeUserDetailsManagerConfigurer中從ApplicationContext獲得。
3.3 InitializeAuthenticationProviderBeanManagerConfigurer
該類的目的純粹是為了添加InitializeUserDetailsManagerConfigurer配置,通過在其configure方法階段從ApplicationContext中得到AuthenticationProvider類型的Bean,并加入到ProviderManager中
@Order(InitializeAuthenticationProviderBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeAuthenticationProviderBeanManagerConfigurer
extends GlobalAuthenticationConfigurerAdapter {
static final int DEFAULT_ORDER = InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER
- 100;
private final ApplicationContext context;
/**
* @param context the ApplicationContext to look up beans.
*/
InitializeAuthenticationProviderBeanManagerConfigurer(
ApplicationContext context) {
this.context = context;
}
@Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.apply(new InitializeUserDetailsManagerConfigurer());
}
class InitializeUserDetailsManagerConfigurer
extends GlobalAuthenticationConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) {
if (auth.isConfigured()) {
return;
}
//直接從ApplicationContext中得到AuthenticationProvider類型Bean
//也就是說,我們可以自定義一個,直接加入到ApplicationContext就可生效了,方便擴展
AuthenticationProvider authenticationProvider = getBeanOrNull(
AuthenticationProvider.class);
if (authenticationProvider == null) {
return;
}
//這里直接添加到DefaultPasswordEncoderAuthenticationManagerBuilder中
auth.authenticationProvider(authenticationProvider);
}
/**
* @return
*/
private <T> T getBeanOrNull(Class<T> type) {
String[] userDetailsBeanNames = InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBeanNamesForType(type);
if (userDetailsBeanNames.length != 1) {
return null;
}
return InitializeAuthenticationProviderBeanManagerConfigurer.this.context
.getBean(userDetailsBeanNames[0], type);
}
}
}
小結:
Spring Security默認給我們創建了一個支持UsernamePasswordAuthenticationToken認證的AuthenticationProvider,里面用到了從ApplicationContext中取到的UserDetailsService、PasswordEncoder和UserDetailsPasswordService的實例對象;
我們可以自定義AuthenticationProvider實例來添加其它類型的驗證工作,同時,只需要將實例對象添加到ApplicationContext容器中即可生效。
經過上來的步驟后,在DefaultPasswordEncoderAuthenticationManagerBuilder的authenticationProviders屬性中添加了一個或多個AuthenticationProvider,接下來的工作便是執行DefaultPasswordEncoderAuthenticationManagerBuilder的performBuild()方法完成AuthenticationManager的創建工作。當然,其實該方法是在父類AuthenticationManagerBuilder中的。
public class AuthenticationManagerBuilder
extends
AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
private final Log logger = LogFactory.getLog(getClass());
private AuthenticationManager parentAuthenticationManager;
private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
private UserDetailsService defaultUserDetailsService;
private Boolean eraseCredentials;
private AuthenticationEventPublisher eventPublisher;
@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;
}
}
返回的其實是ProviderManager,而ProviderManager可以看成是AuthenticationManager的代理對象,里面保存了多個AuthenticationManager的實現。
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
InitializingBean {
// ~ Static fields/initializers
// =====================================================================================
private static final Log logger = LogFactory.getLog(ProviderManager.class);
// ~ Instance fields
// ================================================================================================
private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
private List<AuthenticationProvider> providers = Collections.emptyList();
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//可以是ProviderManager,目的是當無法進行身份認證時,再使用這個AuthenticationManager進行認證
private AuthenticationManager parent;
private boolean eraseCredentialsAfterAuthentication = true;
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");
}
}
@Override
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();
//查找匹配的 AuthenticationProvider 來執行驗證
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
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 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;
}
}
Spring Security默認情況下為我們創建了一個基于用戶名和密碼進行驗證的AuthenticationManager實例,同時收集ApplicationContext中的AuthenticationProvider類型的Bean 一起添加到ProviderManager(AuthenticationManager的子類)中供需要的地方進行使用。