AuthenticationManager 的 authentication 过程

1. 结论
// 调用链
AuthenticationManager.authenticate()  --> ProviderManager.authenticate() --> DaoAuthenticationProvider(AbstractUserDetailsAuthenticationProvider).authenticate()
// 处理
在最后的 authenticate() 方法中,调用了 UserDetailsService.loadUserByUsername() 并进行了密码校验,校验成功就构造一个认证过的 UsernamePasswordAuthenticationToken 对象放入 SecurityContext.
2. AuthenticationManager.authenticate()
public interface AuthenticationManager {
	// 就这一个方法
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}

ProviderManager 是它的是实现类,实际上调用的它的 authentication() 方法

image-20210726112523112

3. ProviderManager.authentication()
public class ProviderManager implements AuthenticationManager, MessageSourceAware,
		InitializingBean {

	// authenticate 方法
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
    	
    	// getProviders() -> debug发现其实就一个 DaoAuthenticationProvider.class
		for (AuthenticationProvider provider : getProviders()) {
            // toTest == UsernamePasswordAuthenticationToken.class
            // 其实就是判断 toTest 是不是UsernamePasswordAuthenticationToken类,成立
			if (!provider.supports(toTest)) {
				continue;
			}

			try {
=========================================================================================
				// 最终 DaoAuthenticationProvider.authenticate()
    			result = provider.authenticate(authentication);
=========================================================================================
}
4. DaoAuthenticationProvider.authenticate()

DaoAuthenticationProvider 的父类是 AbstractUserDetailsAuthenticationProvider,实际上调用的是父类的 authentication(),因为继承关系,所以有时候不好判断到底是父类的方法还是子类重写后的方法,建议 debug,非常清楚。

image-20210726114003984

这里主要有三个步骤:

  1. 子类的 retrieveUser(),里面调用了 UserDetailsService.loadUserByUsername() 进行身份查找
  2. 子类的 additionalAuthenticationChecks(),里面调用 passwordEncoder.matches() 进行密码匹配
  3. 子类的 createSuccessAuthentication(),认证完成,往 SecurityContext中放一个认证过的 auth 对象
public abstract class AbstractUserDetailsAuthenticationProvider implements
		AuthenticationProvider, InitializingBean, MessageSourceAware {

	// authentication
	public Authentication authenticate(Authentication authentication)
			throws AuthenticationException {
    		// ....
========================================================================================
			try {
                // 1. 这个 retrieveUser ,就是判断用户身份,查询内存或者数据库拿到用户对象
				user = retrieveUser(username,
						(UsernamePasswordAuthenticationToken) authentication);
			}
			
			// 2. 密码校验
			additionalAuthenticationChecks(user,
					(UsernamePasswordAuthenticationToken) authentication);

		Object principalToReturn = user;

		// 3. 返回一个认证过的对象,注意这里 principle 传的是保存了用户信息的 user 对象,不是 String
		return createSuccessAuthentication(principalToReturn, authentication, user);
	}
5. DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    // 1. retrieveUser(),用户身份判断
    protected final UserDetails retrieveUser(String username,
      UsernamePasswordAuthenticationToken authentication) {
   try {
=========================================================================================
    // 就是在这里调用了 UserDetailsService  
    UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
=========================================================================================

} 
// 2. additionalAuthenticationChecks() 密码校验
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"));
		}
	}
    // 3. 创建一个已认证的对象
    protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		boolean upgradeEncoding = this.userDetailsPasswordService != null
				&& this.passwordEncoder.upgradeEncoding(user.getPassword());
        // 这里又回到 父类了,无语
		return super.createSuccessAuthentication(principal, authentication, user);
	}
    // 直接粘贴在这里 super.createSuccessAuthentication()
    protected Authentication createSuccessAuthentication(Object principal,
			Authentication authentication, UserDetails user) {
		// 调用 UsernamePasswordAuthenticationToken 类 三个构造参数的构造器(认证过的对象创建)
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				principal, authentication.getCredentials(),
				authoritiesMapper.mapAuthorities(user.getAuthorities()));
		result.setDetails(authentication.getDetails());

		return result;
	}
6. UsernamePasswordUnthenticationToken构造器

为什么说选择三个参数的构造器构造的就是一个已认证的对象。我们来看看

/**
 * 两个参数,构造待认证对象
 * principle 传 username
 * credentials 传 passord
 */
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
   super(null);
   this.principal = principal;
   this.credentials = credentials;
   // 设置标识,未认证
   setAuthenticated(false);
}

/**
 * This constructor should only be used by <code>AuthenticationManager</code> or
 * <code>AuthenticationProvider</code> implementations that are satisfied with
 * producing a trusted (i.e. {@link #isAuthenticated()} = <code>true</code>)
 * authentication token.
 *
 * 两个参数,构造待认证对象
 * principle 传 保存了用户信息的对象(实现了UserDetails接口的对象)
 * credentials 传 passord,也可以传null,因为认证过后,我们不需要知道密码,也是一种保护
 * authorities 传 用户的权限列表
 */
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
      Collection<? extends GrantedAuthority> authorities) {
   super(authorities);
   this.principal = principal;
   this.credentials = credentials;
   // 标记已认证 
   super.setAuthenticated(true); 
}

从这里也可以看出为什么 pinciplecredentials 的类型都是 Object,因为 pinciple 认证前需要传 String 类型的 username,认证后需要传实现了UserDetails 接口的用户对象;而 credentials 认证前需要传前端传来的String类型的password,认证后需要传 String类型的密码 或者 null

通常都是,接收 前端传来的 usernamepassword,调用 两个参数的构造器,创建一个未认证的对象,交给AuthenticationManager进行认证。认证成功后,调用三个参数的构造器,创建一个已认证的对象。

  • 28
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值