文章目录
AbstractUserDetailsAuthenticationProvider
Declared
package org.springframework.security.authentication.dao
public abstract class AbstractUserDetailsAuthenticationProvider
extends java.lang.Object
implements AuthenticationProvider,
org.springframework.beans.factory.InitializingBean,
org.springframework.context.MessageSourceAware
Class JDOC
一个允许子类重写和使用UserDetails对象的基础的AuthenticationProvider。该类被设计用来响应UsernamePasswordAuthenticationToken身份认证请求。
认证成功后,将创建一个UsernamePasswordAuthenticationToken并返回给调用方。令牌将包含用户名的字符串表示形式或从身份验证资源库返回的用户详细信息作为其主体。如果正在使用容器适配器,则使用字符串比较合适,因为它需要用户名的字符串表示形式。如果您需要访问经过认证的用户的其他属性,例如:邮件地址、昵称等,那么使用UserDetails是合适的。由于不推荐使用容器适配器,UserDetails提供了额外的灵活性,默认情况下返回UserDetails。将setForcePrincipalAsString(boolean)设置为true,可以覆盖默认值。
缓存是通过将UserDetails对象放置在UserCache中的来处理的。这样确保可以验证使用相同用户名的后续请求,而无需查询UserDetailsService。需要注意的是,如果用户提供的密码不正确,将查询UserDetailsService以确认使用了最新的密码进行比较。只有无状态应用程序才可能需要缓存。例如,在普通的web应用程序中,SecurityContext存储在用户的会话中,并且用户不会在每次请求时重新验证。因此,默认的缓存实现是NullUserCache。
authenticate
Declared
public Authentication authenticate(Authentication authentication) throws AuthenticationException
Method Code
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);
// 如果没有缓存用户信息,则使用userCache缓存。
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return createSuccessAuthentication(principalToReturn, authentication, user);
}
retrieveUser
Declared
protected abstract UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
Method JDOC
允许子类从特定资源库检索UserDetails,如果提供的凭据不正确,则可以选择立即抛出AuthenticationException(如果需要作为用户绑定到资源以获取或生成UserDetails,则此选项特别有用)。
子类不需要执行任何缓存操作,因为AbstractUserDetailsAuthenticationProvider默认缓存UserDetails。缓存UserDetails确实会带来额外的复杂性。因为这意味着依赖于缓存的后续请求仍然需要验证其凭据,即使在这种方法中采用基于绑定的策略的子类保证凭证的正确性。因此,重要的是子类要么禁用缓存(如果它们希望确保此方法是唯一能够对请求进行身份验证的方法,因为永远不会缓存用户详细信息)或者确保子类实现additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken) 来验证后续认证请求中被缓存UserDetails的凭证。
大多数情况下,子类不会在此方法中执行凭据检查,而是在additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken) 中执行,以便与凭据验证相关的代码不必在两个方法之间重复。
additionalAuthenticationChecks
Decalred
protected abstract void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException;
Method JDOC
允许子类对给定的认证请求的返回(或者缓存)UserDetails执行任何额外的检查。通常,子类至少需要比较Authentication.getCredentials()与UserDetails.getPassword().如果需要自定义逻辑来比较UserDetails和/或UsernamePasswordAuthenticationToken的其他属性,这些属性也应该出现在该方法中。
Parameters
userDetails - 从retrieveUser(String, UsernamePasswordAuthenticationToken) 或者 UserCache 检索到的
authentication - 需要验证的当前请求
createSuccessAuthentication
Declared
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user)
Method Jdoc
创建一个成功的Authentication对象。
Protected,所以子类可以复写这个方法。
子类通常会在返回的Authentication对象中存储用户提供的原始凭证(不是处理过的的或编码过的密码)。
Method Code
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
// 确保我们返回用户提供的原始凭证,这样即使使用编码的密码,后续尝试也会成功。
// 还要确保返回原始的getDetails(),以便在缓存过期后的未来身份验证事件包含详细信息
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}