Spring Security账号密码认证源码解析

一、流程源码分析

1.首先调用了UsernamePasswordAuthenticationFilter的doFilter()方法。

前端请求时,请求信息首先到达UsernamePasswordAuthenticationFilter,该类的继承关系如下。在这里插入图片描述
UsernamePasswordAuthenticationFilter是一个过滤器,所以首先执行doFilter()方法,该方法在它的父类AbstractAuthenticationProcessingFilter 中,方法源码如下:

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)  throws IOException, ServletException {
	// 判断当前过滤器能否处理该请求,如果不能,则交给下一个filter去处理
	if (!requiresAuthentication(request, response)) {
		chain.doFilter(request, response);
		return;
	}
	try {
		// 调用子类中的方法,创建Authentication实现类
		Authentication authenticationResult = attemptAuthentication(request, response);
		if (authenticationResult == null) {
				// return immediately as subclass has indicated that it hasn't completed
				return;
		}
		// 如果成功则做些session相关操作,例如将信息保存到session
		this.sessionStrategy.onAuthentication(authenticationResult, request, response);
		// Authentication success
		if (this.continueChainBeforeSuccessfulAuthentication) {
			chain.doFilter(request, response);
		}
		successfulAuthentication(request, response, chain, authenticationResult);
	}
	catch (InternalAuthenticationServiceException failed) {
		this.logger.error("An internal error occurred while trying to authenticate the user.", failed);
		unsuccessfulAuthentication(request, response, failed);
	}
	catch (AuthenticationException ex) {
		// Authentication failed
		unsuccessfulAuthentication(request, response, ex);
	}
}

2.doFilter方法中调用了自身的attemptAuthentication()方法

在这里插入图片描述

3.attemptAuthentication()方法中调用了AuthenticationManager接口实现类ProviderManager的authenticate方法。

(1)在attemptAuthentication()方法中,只是将账号密码和一些请求信息封装在了一个类中,但并没有对权限,密码是否正确等进行认证,该方法就是用来进行认证管理的。

(2)AuthenticationManager是一个接口,该接口中只有一个authenticate方法。

public interface AuthenticationManager {
    Authentication authenticate(Authentication var1) throws AuthenticationException;
}

(3)该接口的主要实现类为ProviderManager,调用的就是该类的authenticate方法。该类有二个用于认证的成员变量:

private List<AuthenticationProvider> providers;

private AuthenticationManager parent;

AuthenticationProvider是一个接口,是用来提供认证服务的,ProviderManager只是用来管理认证服务的。

// 该接口有两个方法,该接口的实现类主要做认证的。
public interface AuthenticationProvider {

    Authentication authenticate(Authentication var1) throws AuthenticationException;

	// supports是用来检测该类型的认证信息是否可以被自己处理。可以被处理则调用自身的authenticate方法。
    boolean supports(Class<?> var1);
}

(4)ProviderManager的authenticate()方法源码关键部分如下:

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Class toTest = authentication.getClass();
        Object lastException = null;
        Authentication result = null;
        boolean debug = logger.isDebugEnabled();
        // 拿到全部的provider
        Iterator e = this.getProviders().iterator();
        // 遍历provider
        while(e.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)e.next();
            // 挨着个的校验是否支持当前token
            if(provider.supports(toTest)) {
                if(debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    // 找到后直接break,并由当前provider来进行校验工作
                    result = provider.authenticate(authentication);
                    if(result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (AccountStatusException var11) {
                    this.prepareException(var11, authentication);
                    throw var11;
                } catch (InternalAuthenticationServiceException var12) {
                    this.prepareException(var12, authentication);
                    throw var12;
                } catch (AuthenticationException var13) {
                    lastException = var13;
                }
            }
        }
        // 若没有一个支持,则尝试交给父类来执行
        if(result == null && this.parent != null) {
            try {
                result = this.parent.authenticate(authentication);
            } catch (ProviderNotFoundException var9) {
                ;
            } catch (AuthenticationException var10) {
                lastException = var10;
            }
        }
    ..........................
    }

其中会遍历调用AuthenticationProvider实现类对象的supports方法,如果方法返回true,则调用AuthenticationProvider实现类对象的authenticate()方法,该方法是用来进行认证的。
如果该ProviderManager的List<AuthenticationProvider> providers都无法处理,则会调用该ProviderManager的AuthenticationManager parent的authenticate方法,流程一样。

4.AuthenticationManager对象的authenticate方法中调用AuthenticationProvider接口实现类DaoAuthenticationProvider的authenticate方法

**(1) DaoAuthenticationProvider **

对于我们前面封装的UsernamePasswordAuthenticationToken 对象,它的认证处理可以被DaoAuthenticationProvider类进行认证。
DaoAuthenticationProvider类中其实没有定义authenticate方法,它是继承了父类AbstractUserDetailsAuthenticationProvider中的authenticate方法。
AbstractUserDetailsAuthenticationProvider的authenticate()方法源码如下:

// 实现了AuthenticationProvider接口
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {
  
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
        String username = authentication.getPrincipal() == null?"NONE_PROVIDED":authentication.getName();
        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if(user == null) {
            cacheWasUsed = false;

            try {
                // 调用自类retrieveUser
                user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            } catch (UsernameNotFoundException var6) {
                this.logger.debug("User \'" + username + "\' not found");
                if(this.hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
                }

                throw var6;
            }

            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        try {
            /*
             * 前检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,冻结
             * User接口)
             */
            this.preAuthenticationChecks.check(user);
            // 子类具体实现
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        } catch (AuthenticationException var7) {
            if(!cacheWasUsed) {
                throw var7;
            }
            cacheWasUsed = false;
            user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
            this.preAuthenticationChecks.check(user);
            this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
        }
        // 检测用户密码是否过期
        this.postAuthenticationChecks.check(user);
        if(!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if(this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        return this.createSuccessAuthentication(principalToReturn, authentication, user);
    }
}

(2)在该方法中,调用了自身的retrieveUser方法,该方法在DaoAuthenticationProvider中,源码如下:
在这里插入图片描述
该方法首先调用UserDetailsService接口中的loadUserByUsername方法获得数据库或者内存等地方保存的用户信息,所以可以通过实现该接口,可以自定义设置用户信息的来源。
然后将获得到的密码和请求的密码进行比对,如果相同,则方法获取到的用户信息。接着做一些安全检查之类的:

this.preAuthenticationChecks.check(user);
      // 子类具体实现
this.additionalAuthenticationChecks(user,(UsernamePasswordAuthenticationToken)authentication);
 this.postAuthenticationChecks.check(user);

最后该方法返回

return createSuccessAuthentication(principalToReturn, authentication, user);

createSuccessAuthentication方法中调用UsernamePasswordAuthenticationToken的另一个构造器,该构造器会生成具有用户权限和验证通过标识的UsernamePasswordAuthenticationToken类的对象。

5.最后返回到UsernamePasswordAuthenticationFilter的doFilter方法中


二、流程总结

(1)UsernamePasswordAuthenticationFilter的doFilter()方法
(2)UsernamePasswordAuthenticationFilter的attemptAuthentication()方法
在该方法中主要生成了未通过验证的Authentication接口的实现类UsernamePasswordAuthenticationToken 对象。
(3)AuthenticationManager实现类ProviderManager的authenticate()方法
该方法主要通过轮询判断AuthenticationProvider接口实现类DaoAuthenticationProvider能否认证UsernamePasswordAuthenticationToken 对象,
(4)AuthenticationProvider接口实现类DaoAuthenticationProvider的authenticate()方法
该方法主要调用retrieveUser()进行密码判断,对密码正确后的用户信息进行一些其他的安全判断,并最后生通过验证的Authentication接口的实现类UsernamePasswordAuthenticationToken 对象。
(5)AuthenticationProvider接口实现类DaoAuthenticationProvider的retrieveUser()方法
该方法主要进行密码验证
(6)serDetailsService接口中的loadUserByUsername()方法
该方法获取之前保存的用户信息
(7)serDetailsService接口中的loadUserByUsername()方法返回验证结果

(8)最后验证成功,返回UsernamePasswordAuthenticationFilter的doFilter()方法


有问题欢迎指正,也欢迎各位交流讨论,有问题可以问。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值