在 UsernamePasswordAuthenticationFilter 源碼分析 中,最后在類UsernamePasswordAuthenticationFilter
的驗(yàn)證方法 attemptAuthentication()
會(huì)將用戶表單提交過來的用戶名和密碼封裝成對(duì)象委托類 AuthenticationManager
的驗(yàn)證方法 authenticate()
進(jìn)行身份驗(yàn)證蚊丐。
那么本文主要對(duì) AuthenticationManager
的驗(yàn)證方法 authenticate()
驗(yàn)證原理進(jìn)行源碼分析麦备。
AuthenticationManager 相關(guān)類圖
AuthenticationManager 驗(yàn)證過程涉及到的類和接口較多凛篙,先用一張類圖說明各個(gè)類和接口之間的關(guān)系,如下:
-
AuthenticationManager
為認(rèn)證管理接口類栏渺,其定義了認(rèn)證方法authenticate()
呛梆。 -
ProviderManager
為認(rèn)證管理類,實(shí)現(xiàn)了接口AuthenticationManager
磕诊,并在認(rèn)證方法authenticate()
中將身份認(rèn)證委托給具有認(rèn)證資格的AuthenticationProvider
進(jìn)行身份認(rèn)證填物。
ProviderManager
中的成員變量providers [List<AuthenticationProvider>]
存儲(chǔ)了一個(gè)AuthenticationProvider
類型的List
,和 spring security 配置文件相對(duì)應(yīng)秀仲,如下圖:
-
AuthenticationProvider
為認(rèn)證接口類壶笼,其定義了身份認(rèn)證方法authenticate()
神僵。 -
AbstractUserDetailsAuthenticationProvider
為認(rèn)證抽象類,實(shí)現(xiàn)了接口AuthenticationProvider
定義的認(rèn)證方法authenticate()
覆劈。
AbstractUserDetailsAuthenticationProvider
還定義了虛擬方法retrieveUser()
用作查詢數(shù)據(jù)庫用戶信息保礼,以及虛擬方法additionalAuthenticationChecks()
用作身份認(rèn)證。 -
DaoAuthenticationProvider
繼承自類AbstractUserDetailsAuthenticationProvider
责语,實(shí)現(xiàn)該類的方法retrieveUser()
和additionalAuthenticationChecks()
炮障。
DaoAuthenticationProvider
中還具有成員變量userDetailsService [UserDetailsService]
用作用戶信息查詢,以及成員變量passwordEncoder [PasswordEncoder]
用作密碼的加密及驗(yàn)證坤候。
DaoAuthenticationProvider
和 spring security 配置文件相對(duì)應(yīng)胁赢,如下圖所示:
流程分析
1、認(rèn)證的入口為 AuthenticationManager
的 authenticate()
方法白筹,鑒于 AuthenticationManager
是接口類智末,因此分析它的實(shí)現(xiàn)類 ProviderManager
谅摄,ProviderManager
的 authenticate()
方法代碼如下:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
-->1 for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
if (debug) {
logger.debug("Authentication attempt using "
+ provider.getClass().getName());
}
try {
-->2 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 = 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 = e;
}
}
if (result != null) {
if (eraseCredentialsAfterAuthentication
&& (result instanceof CredentialsContainer)) {
// Authentication is complete. Remove credentials and other secret data
// from authentication
((CredentialsContainer) result).eraseCredentials();
}
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}"));
}
prepareException(lastException, authentication);
throw lastException;
}
其中:
-
-->1
處的for
循環(huán)從該類的屬性providers[List<AuthenticationProvider>]
中去取到支持該認(rèn)證的AuthenticationProvider
來進(jìn)行認(rèn)證處理。 -
-->2
處代碼為使用支持該認(rèn)證的AuthenticationProvider
對(duì)用戶身份進(jìn)行認(rèn)證系馆,使用該類進(jìn)行認(rèn)證如下文所示送漠。
2、在上文代碼的 -->2
處調(diào)用的代碼 result = provider.authenticate(authentication);
由蘑,使用了 AuthenticationProvider
的 authenticate()
方法進(jìn)行認(rèn)證闽寡,接下來分析該方法,鑒于 AuthenticationProvider
是一個(gè)接口尼酿,因此分析它的實(shí)現(xiàn)類 AbstractUserDetailsAuthenticationProvider
的子類 DaoAuthenticationProvider
的認(rèn)證方法 authenticate()
爷狈,代碼如下所示:
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"Only UsernamePasswordAuthenticationToken is supported"));
// Determine username
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
-->1 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 {
preAuthenticationChecks.check(user);
-->2 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;
}
}
postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
其中:
-
-->1
處的代碼表示調(diào)用方法retrieveUser()
從數(shù)據(jù)庫中加載用戶信息。該方法代碼如下:
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
UserDetails loadedUser;
try {
-->1.1 loadedUser = this.getUserDetailsService().loadUserByUsername(username);
}
catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
passwordEncoder.isPasswordValid(userNotFoundEncodedPassword,
presentedPassword, null);
}
throw notFound;
}
catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(
repositoryProblem.getMessage(), repositoryProblem);
}
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
在上述代碼中 -->1.1
處代碼的意思為:調(diào)用成員變量 userDetailsService
的方法 loadUserByUsername()
加載數(shù)據(jù)層中的用戶信息(是不是很熟悉)裳擎。
-
-->2
處的代碼為調(diào)用方法additionalAuthenticationChecks()
密碼驗(yàn)證淆院,該方法代碼如下:
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
Object salt = null;
if (this.saltSource != null) {
salt = this.saltSource.getSalt(userDetails);
}
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();
->2.1 if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
presentedPassword, salt)) {
logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"));
}
}
->2.1
出代碼調(diào)用了成員變量 passwordEncoder
的校驗(yàn)方法 isPasswordValid()
對(duì)用戶密碼進(jìn)行驗(yàn)證。(是不是很熟悉)