第一章:Spring Security(六):Security登录的用户名和密码的校验逻辑

本文详细解析了Spring Security中登录时的用户名和密码校验流程,从UsernamePasswordAuthenticationFilter过滤器开始,经过ProviderManager的authenticate方法,再到DaoAuthenticationProvider的retrieveUser方法,最终使用PasswordEncoder进行密码匹配。理解这一流程对于自定义认证逻辑至关重要。

文章目录

 

第一节:SpringSecurity(一)入门学习规划

第二节:SpringSecurity(二):SpringSecurity入门

第三节:Spring Security(三):Security中重要的几个类

第四节:Spring Security(四):Security的第一次资源访问流程

第五节:Spring Security(五):Security登陆的超简单例子

第六节:Spring Security(六):Security登录的用户名和密码的校验逻辑


上一节中,我们写了超简单的登录例子,但是所有的代码都指向了如何根据用户名获取用户,但是没有看到密码的校验逻辑,这一节,我们将介绍登录的逻辑。

 

文章目录

 


前言

登录其实也是一个请求,当请求登录逻辑时,Spring Security会走过滤器,而用户名和密码校验的逻辑刚好是UsernamePasswordAuthenticationFilter过滤器处理的,那么我们从这个类来分析整个流程的逻辑。


 

一、用户名和密码的Filter逻辑校验流程

先看下面的流程图:用户发送请求,经过UsernamePasswordAuthenticationFilter过滤器,调用父类的doFilter方法后执行authenticate方法,此方法调用ProviderManager类中的authenticate方法,该方法中会调用DaoAuthenticationProvider父类AbstractUserDetailsAuthenticationProvider中的authenticate方法,这个方法就会调用DaoAuthenticationProvider类中的retrieveUser方法,此时这里本质就是调用UserDetailsService.loadUserByUsername方法,正好和上一节对上,也是为什么我们要实现该接口,重新loadUserByUsername方法,获取到该方法返回接口后,和request中的password进行校验,通过PasswordEncoder.match方法进行校验,下面会具体详解。

 

我们走进UsernamePasswordAuthenticationFilter类,发现doFilter方法在其父类AbstractAuthenticationProcessingFilter继承过来的。

 

 具体的逻辑在UsernamePasswordAuthenticationFilter类中,找到这个类的代码

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            String username = this.obtainUsername(request);
            username = username != null ? username : "";
            username = username.trim();
            String password = this.obtainPassword(request);
            password = password != null ? password : "";
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

 this.getAuthenticationManager()方法返回的是ProviderManager对象,调用了authenticate方法,我们继续往下走。

如上图,走到红框的地方,可以看到,provider默认是DaoAuthenticationProvider的实例对象,这里调用的是父类中继承的方法,如下图。

 

上图中,我们看到重要的两个部分,一个是retrieveUser方法,另外一个是additionalAuthenticationChecks方法;先看retrieveUser方法,底层调用的就是UserDetailsService.loadUserByUsername方法,其实就是上一节中重写的UserDetailsService接口的loadUserByUsername方法,如下图:

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);
		}
	}

 第二个红框的方法如下,其实调用的就是下图红框中的密码校验规则,所以其实就是调用了PasswordEncoder类中的match方法,该方法校验的是loadUserByUsername返回的用户对象(该对象可以是内存对象,数据库对象等)中的加密密码和页面表单请求传递过来的明文密码进行校验,校验规则可以看BCryptPasswordEncoder类的源码。

看到这里,其实我们已经弄清楚了,用户登录的用户名和密码的校验过程,其实就是过滤器来做的。如果校验失败,就会抛异常,最后跳转到登录页面,如果校验成功,就会将用户信息缓存起来,并跳转到指定的访问资源。


总结

用户发送请求--》UsernamePasswordAuthenticationFilter过滤器--》调用UserDetailsService.loadUserByUsername方法--》将获得的结果通过PasswordEncoder.match方法来进行校验密码的正确性。最终我们在写代码的时候,只需要考虑重新UserDetailsService接口,在Spring的IOC容器中注入PasswordEncoder实例对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值