SpringSecurity 用户名和密码的校验过程及自定义密码验证

1、源码执行过程

1.执行 AuthenticationManager 认证方法 authenticate(UsernamePasswordAuthenticationToken)

2.ProviderManager 实现了 authenticate(UsernamePasswordAuthenticationToken)

3.ProviderManager 是通过自身管理的n个AuthenticationProvider认证提供者去进行认证

4.AuthenticationProvider认证提供者 使用自身的authenticate(Authentication)方法;

5.AuthenticationProvider的authenticate(Authentication)方法是被AbstractUserDetailsAuthenticationProvider所实现

6.AbstractUserDetailsAuthenticationProvider 抽象用户细节认证提供者 会调用 自身声明的retrieveUser抽象方法来检索用户

7.retrieveUser抽象方法在DaoAuthenticationProvider 持久层认证提供者 中进行了体现

8.DaoAuthenticationProvider 持久层认证提供者 包含 UserDetailsService 用户细节处理器,

9.用户细节处理器的loadUserByUsername方法又被自定义的UserDetailsServiceImpl所实现

10.UserDetailsServiceImpl实现类取出数据库中的该登录名的数据(selectUserByUserName),并将用户的菜单权限数据和基本信息封装成一个UserDetails用户细节返回!

11.AbstractUserDetailsAuthenticationProvider 抽象用户细节认证提供者 最终获取到UserDeatils

12.然后AbstractUserDetailsAuthenticationProvider 调用additionalAuthenticationChecks方法对用户的密码进行最后的检查

13.密码的校验是由BCryptPasswordEncoder 通过实现PasswordEncoder 的matches方法来完成,

14.BCryptPasswordEncoder .matches 方法会校验密文是否属于自己的编码格式,最终密码校验的细节完全在BCrypt实体类中进行

 

###BCrypt 如何判断 密码和数据库的密码是否相同的?

首先BCrypt 是从数据库的密码中提取加密的盐值,并校验数据库密码的长度不能小于28和数据库密码的版本(前两个字符必须是"$2")

然后从数据库密码中获取真正的盐值,然后将用户提交的密码结合盐值加密,对比加密后的密码与数据库是否一致

当然特殊情况下你可以在BCrypt 源码的第345行打断点,再直接操作数据库就可以修改密码

SpringSecurity 用户名和密码的校验过程及自定义密码验证

2、密码验证机制

最近在做Spring Security的授权认证
做完之后再进行登陆操作时出现了这样的错误

Encoded password does not look like BCrypt

这个错误刚出来的时候我是很懵的,毕竟这个所谓的Encoded password 究竟在哪里?只能硬着头皮去查源码,在
DaoAuthenticationProvider文件中可以发现如下代码。

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

这里的presentedPassword是经由前端发送的明文密码,后面的则是由该类调用的函数经过数据库操作之后返回的传送过来的userDetails对象,于是接着看一下passwordEncoder.matches()这个函数

boolean matches(CharSequence rawPassword, String encodedPassword);

	/**
	 * Returns true if the encoded password should be encoded again for better security,
	 * else false. The default implementation always returns false.
	 * @param encodedPassword the encoded password to check
	 * @return true if the encoded password should be encoded again for better security,
	 * else false.
	 */

这是有关该方法的注释,可以看到第一个参数是未加密的密码,第二个参数是加密后的密码
我所用的加密方法是BCryptPasswordEncoder,因此直接找到这个类看他的实现如何。

public boolean matches(CharSequence rawPassword, String encodedPassword) {
		if (rawPassword == null) {
			throw new IllegalArgumentException("rawPassword cannot be null");
		}

		if (encodedPassword == null || encodedPassword.length() == 0) {
			logger.warn("Empty encoded password");
			return false;
		}

		if (!BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
			logger.warn("Encoded password does not look like BCrypt");
			return false;
		}

		return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
	}

发现了错误代码的提示,这就意味着我存在数据库里的信息不能够是未加密后的密码,必须是经过加密后的密码,在数据库中插入几条信息,密码在加密后再存进去,问题得以解决

3、自定义验证方式

我的用户密码前台输入后,需要和用户名关联进行加密比较,所以重写了AuthenticationProvider的实现类进行处理;


@Component
public class MyAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private PasswordEncorder passwordEncorder;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String presentedPassword = (String)authentication.getCredentials();
        UserDetails userDeatils = null;
        // 根据用户名获取用户信息
        SysUser sysUser = this.iSysUserService.getUserByName(username);
        if (StringUtils.isEmpty(sysUser)) {
            throw new BadCredentialsException("用户名不存在");
        } else {
            userDeatils = new User(username, sysUser.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
            // 自定义的加密规则,用户名、输的密码和数据库保存的盐值进行加密
            String encodedPassword = PasswordUtil.encrypt(username, presentedPassword, sysUser.getSalt());
            if (authentication.getCredentials() == null) {
                throw new BadCredentialsException("登录名或密码错误");
            } else if (!this.passwordEncorder.matches(encodedPassword, userDeatils.getPassword())) {
                throw new BadCredentialsException("登录名或密码错误");
            } else {
                UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDeatils, authentication.getCredentials(), userDeatils.getAuthorities());
                result.setDetails(authentication.getDetails());
                return result;
            }
        }
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

 

然后在SecurityConfiguration配置中启用
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.authenticationProvider(this.myAuthenticationProvider);
}

 

上一篇:代码中自定义通过163邮箱发送邮件时提示smtplib.SMTPAuthenticationError: (535, b‘Error: authentication failed‘)


下一篇:PortSwigger Academy | Authentication : 身份认证