您的位置:首页 > 健康 > 养生 > SpringSecurity源码:实现UserDetailsService来处理加密逻辑的

SpringSecurity源码:实现UserDetailsService来处理加密逻辑的

2024/12/23 8:13:04 来源:https://blog.csdn.net/qq_40453972/article/details/140287637  浏览:    关键词:SpringSecurity源码:实现UserDetailsService来处理加密逻辑的

概述:带着好奇和修改密码验证逻辑的心态去探索和修改这部分,直接进入源码分析

1.调用authenticationManager.authenticate方法

当我们后台实现登录的接口的时候,会出现以下密码验证(我们就会好奇,密码怎么验证的)

import org.springframework.security.authentication.AuthenticationManager;@Autowiredprivate AuthenticationManager authenticationManager;************************authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

2.AuthenticationManager-认证管理接口类

定义了身份认证的方法 authenticate(),进入(ctrl+authenticate)源码看,你就会发现它是一个接口类,

package org.springframework.security.authentication;import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;public interface AuthenticationManager {Authentication authenticate(Authentication authentication)throws AuthenticationException;
}

3.ProviderManager类(认证管理类),实现了AuthenticationManager接口类

providerManager 为认证管理类:只是用来管理认证服务的,实现了接口 AuthenticationManager ,并在认证方法 authenticate() 中将身份认证委托具有认证资格的 AuthenticationProvider 进行身份认证

public class ProviderManager implements AuthenticationManager, MessageSourceAware,InitializingBean {private AuthenticationEventPublisher eventPublisher = new NullEventPublisher();
//用于认证的成员变量private List<AuthenticationProvider> providers = Collections.emptyList();protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
//用于认证的成员变量private AuthenticationManager parent;private boolean eraseCredentialsAfterAuthentication = true;public Authentication authenticate(Authentication authentication)throws AuthenticationException {Class<? extends Authentication> toTest = authentication.getClass();AuthenticationException lastException = null;AuthenticationException parentException = null;Authentication result = null;Authentication parentResult = null;boolean debug = logger.isDebugEnabled();// 遍历全部的providerfor (AuthenticationProvider provider : getProviders()) {
// 挨个的校验是否支持当前tokenif (!provider.supports(toTest)) {continue;}if (debug) {logger.debug("Authentication attempt using "+ provider.getClass().getName());}try {
// 找到后直接break,并由当前provider来进行校验工作result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}
catch (AccountStatusException | InternalAuthenticationServiceException e) {prepareException(e, authentication);throw e;} catch (AuthenticationException e) {lastException = e;}}
// 若没有一个支持,则尝试交给父类来执行if (result == null && parent != null) {try {result = parentResult = parent.authenticate(authentication);}
*************************
a.该类有二个用于认证的成员变量
private List<AuthenticationProvider> providers = Collections.emptyList();
private AuthenticationManager parent;b.AuthenticationProvider是一个接口,是用来提供认证服务的
public interface AuthenticationProvider {Authentication authenticate(Authentication authentication)throws AuthenticationException;// supports是用来检测该类型的认证信息是否可以被自己处理。可以被处理则调用自身的authenticate方法。boolean supports(Class<?> authentication);
}

4 DaoAuthenticationProvider类

  • 是继承类AbstractUserDetailsAuthenticationProvider 该方法实现retrieveUser() 和additionalAuthenticationChecks() 两个方法。
  • AuthenticationManager对象的authenticate方法中调用AuthenticationProvider接口实现类DaoAuthenticationProvider的authenticate方法。
  •  对于我们前面封装的UsernamePasswordAuthenticationToken 对象,它的认证处理可以被DaoAuthenticationProvider类进行认证。
  • 具有成员变量 userDetailsService [UserDetailsService] 用作用户信息查询,是在retrieveUser方法中调用用户查询数据库用户信息。
  • 具有成员变量 passwordEncoder [PasswordEncoder] 用作密码的加密及验证。在additionalAuthenticationChecks方法中使用,用于身份的认证。
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";private PasswordEncoder passwordEncoder;private volatile String userNotFoundEncodedPassword;private UserDetailsService userDetailsService;private UserDetailsPasswordService userDetailsPasswordService;public DaoAuthenticationProvider() {setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());}@SuppressWarnings("deprecation")protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {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"));}}protected void doAfterPropertiesSet() {Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");}protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);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);}}@Overrideprotected 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);user = this.userDetailsPasswordService.updatePassword(user, newPassword);}return super.createSuccessAuthentication(principal, authentication, user);}private void prepareTimingAttackProtection() {if (this.userNotFoundEncodedPassword == null) {this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);}}private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {if (authentication.getCredentials() != null) {String presentedPassword = authentication.getCredentials().toString();this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);}}
**************************
}

可以看得出来:DaoAuthenticationProvider类中其实没有定义authenticate方法,它是继承了父类AbstractUserDetailsAuthenticationProvider中的authenticate方法。

5.AbstractUserDetailsAuthenticationProvider-认证的抽象类

  • 它实现了AuthenticationProvider 定义的认证方法authenticate();
  • 还定义了虚拟方法retrieveUser() 用于查询数据库用户信息
  • additionalAuthenticationChecks() 用户身份的认证(这两个方法都是抽象方法)
// 实现了AuthenticationProvider接口
public abstract class AbstractUserDetailsAuthenticationProvider implementsAuthenticationProvider, 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"));// Determine usernameString username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED": authentication.getName();boolean cacheWasUsed = true;UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {
// 调用自类retrieveUseruser = 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 {
//前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结User接口)
//然后将获得到的密码和请求的密码进行比对,如果相同,则方法获取到的用户信息。接着做一些安全检查之类的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;}}
// 检测用户密码是否过期postAuthenticationChecks.check(user);if (!cacheWasUsed) {this.userCache.putUserInCache(user);}Object principalToReturn = user;if (forcePrincipalAsString) {principalToReturn = user.getUsername();}
//最后该方法返回return createSuccessAuthentication(principalToReturn, authentication, user);}
*******************************

5.1.(重要)retrieveUser方法源码,DaoAuthenticationProvider类中

在该方法中,调用了自身的retrieveUser方法

  • 这里是调用成员变量 userDetailsService 的方法 loadUserByUsername() 加载数据层中的用户信息
	protected final UserDetails retrieveUser(String username,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {
//该方法首先调用UserDetailsService接口中的loadUserByUsername方法
//获得数据库或者内存等地方保存的用户信息,所以可以通过实现该接口,
//可以自定义设置用户信息的来源UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}
**********************************

5.2.(重要)additionalAuthenticationChecks-方法密码验证-DaoAuthenticationProvider类

  • passwordEncoder.matches方法

	protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {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"));}}
public interface PasswordEncoder {String encode(CharSequence rawPassword);boolean matches(CharSequence rawPassword, String encodedPassword);default boolean upgradeEncoding(String encodedPassword) {return false;}
}

所以我们可以定义方法去实现这个接口,重写match方法,来达到对加密方法的修改

部分引用了 AuthenticationManager 源码解析

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com