AuthenticationManager 对传入的 Authentication 对象进行身份认证。在认证过程中,传入的 Authentication 参数仅包含用户名、密码等基础属性。认证成功后,返回的 Authentication 对象将会包含完整的属性信息,其中包括用户的角色信息。
主要的作用是协调多个 AuthenticationProvider 来进行身份验证,最主要的实现类是 ProviderManager
具体执行身份验证,每个实现类负责一种验证方法(例如 用户名密码、OAuth、记住我)
authenticate(Authentication authentication): 进行身份验证,返回认证后的 Authentication 对象。supports(Class<?> authentication): 判断是否支持特定类型的 Authentication。又开始讲源码了,值得注意的地方:
默认不启用用户缓存对象
可设置是否详细展示用户信息,例如用户名输入错误,默认只会提示「用户名或密码输入错误」
登录前和登录后都可以做检查,登录前检查用户状态是否正常、是否过期等,登录后检查密码是否过期
通过一个抽象方法完成具体的密码校验工作:
protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
核心校验方法,可以看到在该方法里将上边提到的缓存、登录前后检查等机制都用上了:
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 {
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);
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);
}
AbstractUserDetailsAuthenticationProvider 目前唯一的实现类,主要是补充了几个抽象方法,例如获取 UserDetails、密码匹配等
比较细节的地方:当找不到用户信息的时候,不会直接返回,而是将其与一个「假密码」进行匹配,避免对于攻击者来说,根据接口响应时间就能判断到底是用户不存在还是密码错误,源码:
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.get().matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
AuthenticationManager 的一个重要实现类