计算机系统应用教程网站

网站首页 > 技术文章 正文

Spring Security填坑: Encoded password does not look like BCrypt

btikc 2024-09-25 15:17:30 技术文章 20 ℃ 0 评论

一、前言背景:

简单的说一下吧,我所使用的spring security版本是5.4.2,在写登录逻辑的时候,老是报:Encoded password does not look like BCrypt,但是我明明记得数据库里面的字段是用BCryptPasswordEncoder这个类的encode方法加密生成的呀,很确定就是BCrypt类型,感觉没毛病,但就是报这个错,也上网上找了好多文章,有的是写成了用户名导致的,有的说是数据库密码字符串前面需要加加密类型,不一而足,但是我这情况还不一样,有时候真是给自己蠢哭了,留在结尾吧。。


二、意外问题解决

不过就在debug代码的过程中,明白了security关于密码加密的逻辑,简单介绍一下,这就不得不介绍一下密码加密工厂类PasswordEncoderFactories,它里面有这样一个静态方法

public static PasswordEncoder createDelegatingPasswordEncoder() {
        String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new LdapShaPasswordEncoder());
        encoders.put("MD4", new Md4PasswordEncoder());
        encoders.put("MD5", new MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new StandardPasswordEncoder());
        encoders.put("argon2", new Argon2PasswordEncoder());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }

集中定义了好多加密算法,默认就是bcrypt类型,假设你已经对spring security有一定了解,且自己实现了UserDetailsService接口,最终会在DaoAuthenticationProvider这个类的这个方法

protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

里面进行密码校验,passwordEncoder就是你项目里面定义的密码加密类型,如果你没有显式定义,框架会默认调用上面提到的密码加密工厂类PasswordEncoderFactories生成的加密类型DelegatingPasswordEncoder选择符合加密类型的加密算法实现类,用其matches方法进行密码校验,如果你已经在你的配置类里面显示的声明了加密实现类,如

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

那么在DaoAuthenticationProvider这个类的密码校验方法就会直接进到BCryptPasswordEncoder的matches方法进行校验,而不再进行加密类型的选择,这里要注意显式声明与否时对应的数据库里面密码的字符串格式。

具体就是当显示声明密码加密类型后,数据库里面的密码字段直接存储加密后的字符串即可,例如:我的密码是pass,加密后直接存储就是:$2a$10$yhKm0EE.dobwxXrDqMvSmuMTGcOp6/zHOQZAwpNkwq2tcg8Ze/7oG;当未显式声明密码加密类型时,数据库里面的密码字段的字符串存储格式为:{加密类型}+加密后的字符串,例如我用BCryptPasswordEncoder加密的密码,那么就是:{bcrypt}$2a$10$yhKm0EE.dobwxXrDqMvSmuMTGcOp6/zHOQZAwpNkwq2tcg8Ze/7oG

注意:这两种不能混用、而且必须、也仅能二选其一

三、我的问题最后调试发现是数据库密码字段长度设置小了,因为我用的是mysql5.7,而5.7的mode是STRICT_TRANS_TABLES,也就是严格模式。超过长度被截取了,所以再看看BCryptPasswordEncoder这个类的正则校验

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

除了前面不啦不啦一些校验后面还有53个字符呢,所以。。。。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表