在 UsernamePasswordAuthenticationFilter 源码分析 中,最后在类UsernamePasswordAuthenticationFilter
的验证方法 attemptAuthentication()
会将用户表单提交过来的用户名和密码封装成对象委托类 AuthenticationManager
的验证方法 authenticate()
进行身份验证。
那么本文主要对 AuthenticationManager
的验证方法 authenticate()
验证原理进行源码分析。
AuthenticationManager 相关类图
AuthenticationManager 验证过程涉及到的类和接口较多,先用一张类图说明各个类和接口之间的关系,如下:
-
AuthenticationManager
为认证管理接口类,其定义了认证方法authenticate()
。 -
ProviderManager
为认证管理类,实现了接口AuthenticationManager
,并在认证方法authenticate()
中将身份认证委托给具有认证资格的AuthenticationProvider
进行身份认证。
ProviderManager
中的成员变量providers [List
存储了一个] AuthenticationProvider
类型的List
,和 spring security 配置文件相对应,如下图:
-
AuthenticationProvider
为认证接口类,其定义了身份认证方法authenticate()
。 -
AbstractUserDetailsAuthenticationProvider
为认证抽象类,实现了接口AuthenticationProvider
定义的认证方法authenticate()
。
AbstractUserDetailsAuthenticationProvider
还定义了虚拟方法retrieveUser()
用作查询数据库用户信息,以及虚拟方法additionalAuthenticationChecks()
用作身份认证。 -
DaoAuthenticationProvider
继承自类AbstractUserDetailsAuthenticationProvider
,实现该类的方法retrieveUser()
和additionalAuthenticationChecks()
。
DaoAuthenticationProvider
中还具有成员变量userDetailsService [UserDetailsService]
用作用户信息查询,以及成员变量passwordEncoder [PasswordEncoder]
用作密码的加密及验证。
DaoAuthenticationProvider
和 spring security 配置文件相对应,如下图所示:
流程分析
1、认证的入口为 AuthenticationManager
的 authenticate()
方法,鉴于 AuthenticationManager
是接口类,因此分析它的实现类 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
循环从该类的属性providers[List
中去取到支持该认证的] AuthenticationProvider
来进行认证处理。 -
-->2
处代码为使用支持该认证的AuthenticationProvider
对用户身份进行认证,使用该类进行认证如下文所示。
2、在上文代码的 -->2
处调用的代码 result = provider.authenticate(authentication);
,使用了 AuthenticationProvider
的 authenticate()
方法进行认证,接下来分析该方法,鉴于 AuthenticationProvider
是一个接口,因此分析它的实现类 AbstractUserDetailsAuthenticationProvider
的子类 DaoAuthenticationProvider
的认证方法 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
处的代码表示调用方法retrieveUser()
从数据库中加载用户信息。该方法代码如下:
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
处代码的意思为:调用成员变量 userDetailsService
的方法 loadUserByUsername()
加载数据层中的用户信息(是不是很熟悉)。
-
-->2
处的代码为调用方法additionalAuthenticationChecks()
密码验证,该方法代码如下:
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
出代码调用了成员变量 passwordEncoder
的校验方法 isPasswordValid()
对用户密码进行验证。(是不是很熟悉)
附
参考
- spring security 源码
- spring security API