AuthenticationProvider可以处理特定的Authentication实现。
Spring Security:身份验证令牌Authentication介绍与Debug分析
Authentication接口及其实现类如下图所示:
AuthenticationProvider接口及其实现类如下图所示:
public interface AuthenticationProvider {
Authentication authenticate(Authentication authentication)
throws AuthenticationException;
boolean supports(Class> authentication);
}
TestingAuthenticationProvider
验证TestingAuthenticationToken的AuthenticationProvider实现,此实现的目的是用于单元测试,不应在生产环境中使用它。
public class TestingAuthenticationProvider implements AuthenticationProvider {
// 直接返回authentication实例,用于单元测试
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
return authentication;
}
// 支持TestingAuthenticationToken类型的身份验证请求令牌
public boolean supports(Class> authentication) {
return TestingAuthenticationToken.class.isAssignableFrom(authentication);
}
}
AnonymousAuthenticationProvider
验证AnonymousAuthenticationToken的AuthenticationProvider实现。要成功验证,AnonymousAuthenticationToken.getKeyHash()必须与此类的getKey()匹配。
public class AnonymousAuthenticationProvider implements AuthenticationProvider,
MessageSourceAware {
// 用于访问来自MessageSource的消息
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 识别Authentication实例是否由授权客户生成的key
private String key;
// 使用key构造AnonymousAuthenticationProvider实例
public AnonymousAuthenticationProvider(String key) {
Assert.hasLength(key, "A Key is required");
this.key = key;
}
// 进行验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 是否支持该Authentication实例
if (!supports(authentication.getClass())) {
return null;
}
// AnonymousAuthenticationToken.getKeyHash()必须与此类的getKey()匹配才能验证成功
if (this.key.hashCode() != ((AnonymousAuthenticationToken) authentication)
.getKeyHash()) {
throw new BadCredentialsException(
messages.getMessage("AnonymousAuthenticationProvider.incorrectKey",
"The presented AnonymousAuthenticationToken does not contain the expected key"));
}
return authentication;
}
// 返回key属性
public String getKey() {
return key;
}
// 设置messages属性
public void setMessageSource(MessageSource messageSource) {
Assert.notNull(messageSource, "messageSource cannot be null");
this.messages = new MessageSourceAccessor(messageSource);
}
// 支持AnonymousAuthenticationToken类型
public boolean supports(Class> authentication) {
return (AnonymousAuthenticationToken.class.isAssignableFrom(authentication));
}
}
RememberMeAuthenticationProvider
验证RememberMeAuthenticationToken的AuthenticationProvider实现。要成功验证, RememberMeAuthenticationToken.getKeyHash()必须与此类的getKey()匹配。
public class RememberMeAuthenticationProvider implements AuthenticationProvider,
InitializingBean, MessageSourceAware {
// 用于访问来自MessageSource的消息
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 识别Authentication实例是否由授权客户生成的key
private String key;
// 使用key构造AnonymousAuthenticationProvider实例
public RememberMeAuthenticationProvider(String key) {
Assert.hasLength(key, "key must have a length");
this.key = key;
}
// 检查messages属性是否为null
public void afterPropertiesSet() {
Assert.notNull(this.messages, "A message source must be set");
}
// 进行验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 是否支持该Authentication实例
if (!supports(authentication.getClass())) {
return null;
}
// RememberMeAuthenticationToken.getKeyHash()必须与此类的getKey()匹配才能验证成功
if (this.key.hashCode() != ((RememberMeAuthenticationToken) authentication)
.getKeyHash()) {
throw new BadCredentialsException(
messages.getMessage("RememberMeAuthenticationProvider.incorrectKey",
"The presented RememberMeAuthenticationToken does not contain the expected key"));
}
return authentication;
}
// 返回key属性
public String getKey() {
return key;
}
// 设置messages属性
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
// 支持RememberMeAuthenticationToken类型
public boolean supports(Class> authentication) {
return (RememberMeAuthenticationToken.class.isAssignableFrom(authentication));
}
}
RemoteAuthenticationProvider
使用RemoteAuthenticationManager验证身份验证请求的客户端对象。此类创建一个新的Authentication对象,包括请求Authentication对象的principal 、 credentials和RemoteAuthenticationManager返回的GrantedAuthority集合。
public class RemoteAuthenticationProvider implements AuthenticationProvider,
InitializingBean {
// RemoteAuthenticationManager用于允许远程客户端尝试身份验证
private RemoteAuthenticationManager remoteAuthenticationManager;
// 检查remoteAuthenticationManager属性是否为null
public void afterPropertiesSet() {
Assert.notNull(this.remoteAuthenticationManager,
"remoteAuthenticationManager is mandatory");
}
// 进行验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 获取用户名
String username = authentication.getPrincipal().toString();
// 获取凭据
Object credentials = authentication.getCredentials();
// 根据凭据获取密码
String password = credentials == null ? null : credentials.toString();
// 将RemoteAuthenticationManager处理验证请求的结果作为权限集合
Collection extends GrantedAuthority> authorities = remoteAuthenticationManager
.attemptAuthentication(username, password);
// 返回UsernamePasswordAuthenticationToken实例
return new UsernamePasswordAuthenticationToken(username, password, authorities);
}
// 返回remoteAuthenticationManager 属性
public RemoteAuthenticationManager getRemoteAuthenticationManager() {
return remoteAuthenticationManager;
}
// 设置remoteAuthenticationManager 属性
public void setRemoteAuthenticationManager(
RemoteAuthenticationManager remoteAuthenticationManager) {
this.remoteAuthenticationManager = remoteAuthenticationManager;
}
// 支持UsernamePasswordAuthenticationToken类型
public boolean supports(Class> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
}
RemoteAuthenticationManager
允许远程客户端尝试身份验证。
public interface RemoteAuthenticationManager {
Collection extends GrantedAuthority> attemptAuthentication(String username,
String password) throws RemoteAuthenticationException;
}
RemoteAuthenticationManagerImpl
RemoteAuthenticationManager接口的实现类。
public class RemoteAuthenticationManagerImpl implements RemoteAuthenticationManager,
InitializingBean {
// AuthenticationManager实例,用于处理身份验证请求
private AuthenticationManager authenticationManager;
// 检查authenticationManager属性是否为null
public void afterPropertiesSet() {
Assert.notNull(this.authenticationManager, "authenticationManager is required");
}
// 尝试验证
public Collection extends GrantedAuthority> attemptAuthentication(String username,
String password) throws RemoteAuthenticationException {
// 基于用户名和密码创建UsernamePasswordAuthenticationToken实例
UsernamePasswordAuthenticationToken request = new UsernamePasswordAuthenticationToken(
username, password);
try {
// 使用AuthenticationManager处理身份验证请求
// 如果验证成功,则返回授予的权限集合
return authenticationManager.authenticate(request).getAuthorities();
}
catch (AuthenticationException authEx) {
throw new RemoteAuthenticationException(authEx.getMessage());
}
}
// 返回authenticationManager属性
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}
// 设置authenticationManager属性
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
}
PreAuthenticatedAuthenticationProvider
处理预验证的验证请求。 该请求通常来自AbstractPreAuthenticatedProcessingFilter的子类。此身份验证提供程序不会对身份验证请求执行任何检查,因为它们应该已经过预身份验证。但是,例如AuthenticationUserDetailsService实现(允许基于Authentication对象加载UserDetails对象的接口,用于为经过身份验证的用户加载UserDetails)可能仍会引发UsernameNotFoundException。
public class PreAuthenticatedAuthenticationProvider implements AuthenticationProvider,
InitializingBean, Ordered {
private static final Log logger = LogFactory
.getLog(PreAuthenticatedAuthenticationProvider.class);
// 用于为经过身份验证的用户加载UserDetails
private AuthenticationUserDetailsService preAuthenticatedUserDetailsService = null;
// 用于检查加载的UserDetails对象的状态
private UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
// 当令牌(验证请求)被拒绝(验证失败)时是否抛出异常
private boolean throwExceptionWhenTokenRejected = false;
// 默认值,与无序相同
private int order = -1;
public void afterPropertiesSet() {
Assert.notNull(preAuthenticatedUserDetailsService,
"An AuthenticationUserDetailsService must be set");
}
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 是否支持该Authentication实例
if (!supports(authentication.getClass())) {
return null;
}
if (logger.isDebugEnabled()) {
logger.debug("PreAuthenticated authentication request: " + authentication);
}
// 主体为空
if (authentication.getPrincipal() == null) {
logger.debug("No pre-authenticated principal found in request.");
// 是否需要抛出异常
if (throwExceptionWhenTokenRejected) {
throw new BadCredentialsException(
"No pre-authenticated principal found in request.");
}
// 验证失败
return null;
}
// 凭据为空
if (authentication.getCredentials() == null) {
logger.debug("No pre-authenticated credentials found in request.");
// 是否需要抛出异常
if (throwExceptionWhenTokenRejected) {
throw new BadCredentialsException(
"No pre-authenticated credentials found in request.");
}
// 验证失败
return null;
}
// 使用AuthenticationUserDetailsService为经过身份验证的用户加载UserDetails
UserDetails ud = preAuthenticatedUserDetailsService
.loadUserDetails((PreAuthenticatedAuthenticationToken) authentication);
// 检查加载的UserDetails对象的状态
userDetailsChecker.check(ud);
// 根据加载的UserDetails对象和authentication对象创建PreAuthenticatedAuthenticationToken实例
// 该实例用于返回,作为验证结果
PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(
ud, authentication.getCredentials(), ud.getAuthorities());
// 给验证结果设置验证请求的详细信息
result.setDetails(authentication.getDetails());
return result;
}
public final boolean supports(Class> authentication) {
return PreAuthenticatedAuthenticationToken.class.isAssignableFrom(authentication);
}
public void setPreAuthenticatedUserDetailsService(
AuthenticationUserDetailsService uds) {
this.preAuthenticatedUserDetailsService = uds;
}
public void setThrowExceptionWhenTokenRejected(boolean throwExceptionWhenTokenRejected) {
this.throwExceptionWhenTokenRejected = throwExceptionWhenTokenRejected;
}
public void setUserDetailsChecker(UserDetailsChecker userDetailsChecker) {
Assert.notNull(userDetailsChecker, "userDetailsChecker cannot be null");
this.userDetailsChecker = userDetailsChecker;
}
// 返回order属性
public int getOrder() {
return order;
}
// 设置order属性
public void setOrder(int i) {
order = i;
}
}
RunAsImplAuthenticationProvider
可以对RunAsUserToken进行身份验证的AuthenticationProvider实现。与RunAsImplAuthenticationProvider的密钥匹配的哈希码的任何RunAsUserToken实例视为有效。如果密钥不匹配,则会抛出BadCredentialsException。
public class RunAsImplAuthenticationProvider implements InitializingBean,
AuthenticationProvider, MessageSourceAware {
// 用于访问来自MessageSource的消息
protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
// 识别Authentication实例是否由授权客户生成的key
private String key;
// 检查key属性是否为null
public void afterPropertiesSet() {
Assert.notNull(key,
"A Key is required and should match that configured for the RunAsManagerImpl");
}
// 进行验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
// 转换成RunAsUserToken实例
RunAsUserToken token = (RunAsUserToken) authentication;
// 与RunAsImplAuthenticationProvider的密钥匹配的哈希码的任何RunAsUserToken实例视为有效
if (token.getKeyHash() == key.hashCode()) {
return authentication;
}
else {
throw new BadCredentialsException(messages.getMessage(
"RunAsImplAuthenticationProvider.incorrectKey",
"The presented RunAsUserToken does not contain the expected key"));
}
}
// 返回key属性
public String getKey() {
return key;
}
// 设置key属性
public void setKey(String key) {
this.key = key;
}
// 设置messages属性
public void setMessageSource(MessageSource messageSource) {
this.messages = new MessageSourceAccessor(messageSource);
}
// 支持RunAsUserToken类型
public boolean supports(Class> authentication) {
return RunAsUserToken.class.isAssignableFrom(authentication);
}
}
AbstractUserDetailsAuthenticationProvider
一个基本的AuthenticationProvider,它允许子类覆盖和使用UserDetails对象。该类旨在响应UsernamePasswordAuthenticationToken身份验证请求。成功验证后,将创建一个UsernamePasswordAuthenticationToken并将其返回给调用者。
通过存储放置在UserCache中的UserDetails对象来处理缓存。这确保可以验证具有相同用户名的后续请求,而无需查询UserDetailsService。需要注意的是,如果用户出现密码错误,将查询UserDetailsService以确认是否使用了最新密码进行比较。只有无状态应用程序才可能需要缓存。例如,在普通的Web应用程序中, SecurityContext存储在用户的会话中,并且用户不会在每个请求上重新进行身份验证。因此,默认缓存实现是NullUserCache。
public abstract class AbstractUserDetailsAuthenticationProvider implements
AuthenticationProvider, InitializingBean, MessageSourceAware {
protected final Log logger = LogFactory.getLog(getClass());
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;
public final void afterPropertiesSet() throws Exception {
Assert.notNull(this.userCache, "A user cache must be set");
Assert.notNull(this.messages, "A message source must be set");
doAfterPropertiesSet();
}
// 进行验证
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
() -> messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.onlySupports",
"only UsernamePasswordAuthenticationToken is supported"));
// 获取用户名
String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
: authentication.getName();
boolean cacheWasUsed = true;
// 基于用户名,获取UserDetails实例
UserDetails user = this.userCache.getUserFromCache(username);
// 没有该用户
if (user == null) {
cacheWasUsed = false;
try {
// 基于用户名和验证请求加载UserDetails
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) {
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;
// 强制Principal作为字符串
if (forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
// 验证成功后,创建Authentication实例用于返回,表示验证结果
return createSuccessAuthentication(principalToReturn, authentication, user);
}
protected Authentication createSuccessAuthentication(Object principal,
Authentication authentication, UserDetails user) {
// 确保返回用户提供的原始凭据
// 以便后续尝试使用编码密码也能成功
// 还要确保返回原始的getDetails()
// 以便缓存到期后的未来身份验证事件包含详细信息
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
principal, authentication.getCredentials(),
authoritiesMapper.mapAuthorities(user.getAuthorities()));
result.setDetails(authentication.getDetails());
return result;
}
protected void doAfterPropertiesSet() throws Exception {
}
...
protected abstract UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException;
public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
this.forcePrincipalAsString = forcePrincipalAsString;
}
public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
}
...
// 支持UsernamePasswordAuthenticationToken类型
public boolean supports(Class> authentication) {
return (UsernamePasswordAuthenticationToken.class
.isAssignableFrom(authentication));
}
// 默认用于验证前UserDetails检查的UserDetailsChecker实现
private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if (!user.isAccountNonLocked()) {
logger.debug("User account is locked");
throw new LockedException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.locked",
"User account is locked"));
}
if (!user.isEnabled()) {
logger.debug("User account is disabled");
throw new DisabledException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.disabled",
"User is disabled"));
}
if (!user.isAccountNonExpired()) {
logger.debug("User account is expired");
throw new AccountExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.expired",
"User account has expired"));
}
}
}
// 默认用于验证后UserDetails检查的UserDetailsChecker实现
private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
public void check(UserDetails user) {
if (!user.isCredentialsNonExpired()) {
logger.debug("User account credentials have expired");
throw new CredentialsExpiredException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.credentialsExpired",
"User credentials have expired"));
}
}
}
}
DaoAuthenticationProvider
从UserDetailsService加载用户详细信息的AuthenticationProvider实现。
Spring Security:用户服务UserDetailsService源码分析
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
// 密码编码器
private PasswordEncoder passwordEncoder;
private volatile String userNotFoundEncodedPassword;
// 用于加载用户详细信息
private UserDetailsService userDetailsService;
// 用于更改UserDetails密码的API
private UserDetailsPasswordService userDetailsPasswordService;
// 构造器
// 使用PasswordEncoderFactories.createDelegatingPasswordEncoder()的值设置密码编码器
public DaoAuthenticationProvider() {
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
// 验证检查
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
// 如果验证请求的凭据(比如密码)为null,则抛出异常
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"));
}
}
// 检查userDetailsService属性是否为null
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
// 基于验证请求加载UserDetails(用户)
protected final UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// 基于用户名,使用UserDetailsService加载UserDetails
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
// 加载不到UserDetails,则抛出异常
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);
}
}
// 验证成功后,创建Authentication实例用于表示验证请求的结果
@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);
// 使用UserDetailsPasswordService更新用户的密码
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
// 通过调用super来返回
return super.createSuccessAuthentication(principal, authentication, user);
}
// 保护方法,避免SEC-2056(黑客嗅探用户是否存在)
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
// 用户不存在时,将使用userNotFoundEncodedPassword进行密码匹配
//
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
...
}
还有三个AuthenticationProvider接口的实现类,用于验证JaasAuthenticationToken,大家可以自行阅读源码,这里就不进行介绍了。
身份验证提供程序AuthenticationProvider介绍就到这里,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。



