【Spring Security】 PasswordEncoder

Metadata

title: 【Spring Security】 PasswordEncoder
date: 2023-02-02 13:52
tags:
  - 行动阶段/完成
  - 主题场景/组件
  - 笔记空间/KnowladgeSpace/ProgramSpace/ModuleSpace
  - 细化主题/Module/SpringSecurity
categories:
  - SpringSecurity
keywords:
  - SpringSecurity
description: 【Spring Security】 PasswordEncoder

【Spring Security】 PasswordEncoder

在之前使用用户名密码进行认证的时候,我们注入了一个注入密码解析器到 [IOC] 中,DaoAuthenticationProvider 就可以获取到这个密码解析器,并使用它进行输入密码和用户名密码校验,那么它具体是怎么加载和执行的呢? 接下里深入了解下

/**
     * 注入密码解析器到IOC中
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

源码分析

PasswordEncoder

PasswordEncoder 是一个接口,他有众多实现类,这些实现类可以理解为 Security 默认支持的密码解析器。

PasswordEncoder 定义了三个方法,用于加密和密码验证。

public interface PasswordEncoder {
    /**
     * 编码原始密码
     */
    String encode(CharSequence rawPassword);

    /**
     * 验证从存储中获取的编码密码是否与提交的原始密码匹配。密码也经过编码。如果密码匹配,则返回 true,
     * 如果不匹配,则返回 false。存储的密码本身应该永远不会被解码
     */
    boolean matches(CharSequence rawPassword, String encodedPassword);
    /**
     * 升级编码
     */
    default boolean upgradeEncoding(String encodedPassword) {
        return false;
    }
}

BCryptPasswordEncoder

BCryptPasswordEncoder 是使用 BCrypt 强散列函数的 PasswordEncoder 的实现类。

Bcrypt 加密算法,会加入随机盐,相同密码加密后生成的字符串都不一样,比较安全。

$2a$10$iyfe/cmw9PT7gVoZnLqrKOhUI.pntwiWBKmFU8TR.EM6.vS3/y5MG

BCryptPasswordEncoder 源码:

public class BCryptPasswordEncoder implements PasswordEncoder {

    private Pattern BCRYPT_PATTERN = Pattern.compile("\\A\\$2(a|y|b)?\\$(\\d\\d)\\$[./0-9A-Za-z]{53}");

    private final Log logger = LogFactory.getLog(getClass());

    private final int strength;

    private final BCryptVersion version;

    private final SecureRandom random;

    public BCryptPasswordEncoder() {
        this(-1);
    }

    /**
     * @param strength 默认值为10,可选值为4-31;
     */
    public BCryptPasswordEncoder(int strength) {
        this(strength, null);
    }

    /**
     * @param version 版本  默认值为$2a,可选值为$2a, $2b, $2y;
     */
    public BCryptPasswordEncoder(BCryptVersion version) {
        this(version, null);
    }

    /**
     * @param random 生成加密基本的随机数,SecureRandom的实例,默认值为空;
     */
    public BCryptPasswordEncoder(BCryptVersion version, SecureRandom random) {
        this(version, -1, random);
    }

    public BCryptPasswordEncoder(int strength, SecureRandom random) {
        this(BCryptVersion.$2A, strength, random);
    }

    public BCryptPasswordEncoder(BCryptVersion version, int strength) {
        this(version, strength, null);
    }

    /**
     *
     */
    public BCryptPasswordEncoder(BCryptVersion version, int strength, SecureRandom random) {
        if (strength != -1 && (strength < BCrypt.MIN_LOG_ROUNDS || strength > BCrypt.MAX_LOG_ROUNDS)) {
            throw new IllegalArgumentException("Bad strength");
        }
        this.version = version;
        this.strength = (strength == -1) ? 10 : strength;
        this.random = random;
    }

    // 加密
    @Override
    public String encode(CharSequence rawPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        }
        String salt = getSalt();
        return BCrypt.hashpw(rawPassword.toString(), salt);
    }

    // 获取随机盐
    private String getSalt() {
        if (this.random != null) {
            return BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
        }
        return BCrypt.gensalt(this.version.getVersion(), this.strength);
    }

    // 校验密码
    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        }
        if (encodedPassword == null || encodedPassword.length() == 0) {
            this.logger.warn("Empty encoded password");
            return false;
        }
        if (!this.BCRYPT_PATTERN.matcher(encodedPassword).matches()) {
            this.logger.warn("Encoded password does not look like BCrypt");
            return false;
        }
        return BCrypt.checkpw(rawPassword.toString(), encodedPassword);
    }

    //
    @Override
    public boolean upgradeEncoding(String encodedPassword) {
        if (encodedPassword == null || encodedPassword.length() == 0) {
            this.logger.warn("Empty encoded password");
            return false;
        }
        Matcher matcher = this.BCRYPT_PATTERN.matcher(encodedPassword);
        if (!matcher.matches()) {
            throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
        }
        int strength = Integer.parseInt(matcher.group(2));
        return strength < this.strength;
    }

    /**
     * Stores the default bcrypt version for use in configuration.
     * bcrypt 版本
     */
    public enum BCryptVersion {
        $2A("$2a"),
        $2Y("$2y"),
        $2B("$2b");
        private final String version;
        BCryptVersion(String version) {
            this.version = version;
        }
        public String getVersion() {
            return this.version;
        }
    }
}

其他加密器

Argon2PasswordEncoder

Argon2PasswordEncoder 实现使用 Argon2 算法来散列密码。Argon2 是密码哈希竞赛的获胜者。为了打败自定义硬件上的密码破解,Argon2 是一种需要大量内存的故意慢速算法。与其他自适应单向函数一样,应该将其调整为需要大约 1 秒来验证系统上的密码。

SCryptPasswordEncoder

SCryptPasswordEncoder 实现使用 scrypt 算法来散列密码。为了在自定义硬件上破解密码,scrypt 是一种需要大量内存的故意慢速算法。与其他自适应单向函数一样,应该将其调整为需要大约 1 秒来验证系统上的密码。