转载

There is no PasswordEncoder mapped for the id "null"

spring-boot 1.5.3 升级到 2.1.7 出现上述错误,查看MAVEN引用信息,引用的spring security版本为5.1.16,其官方文档地址为: https://docs.spring.io/spring...

原理猜想

报错的代码在这:

package org.springframework.security.crypto.password;
public class DelegatingPasswordEncoder implements PasswordEncoder {

        @Override
        public boolean matches(CharSequence rawPassword,
            String prefixEncodedPassword) {
            String id = extractId(prefixEncodedPassword);
            throw new IllegalArgumentException("There is no PasswordEncoder mapped for the id /"" + id + "/"");
        }
}

根据异常排查,大概的思想是这样:

matches

第2步我给几个例子,帮助学习:

  1. 由密码 {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG 获取到的加密类型为 bcrypt .
  2. 由密码 {noop}password 获取到加密类开地为 noop
  3. 由密码 {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0 中获取到的加密类型为 sha256

具体的出错的逻辑是这样的:

  1. 获取到了密码,比如为 123456 .
  2. spring尝试从 123456 中,获取一个 加密前缀 的东西,获取的值为null。
  3. 4 没有找到算法,则默认调用 UnmappedIdPasswordEncoder
  4. 运行 matches 算法
  5. 抛出异常。

解决问题

spring security支持的列表如下:

String encodingId = "bcrypt";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put(encodingId, new BCryptPasswordEncoder());
        encoders.put("ldap", new org.springframework.security.crypto.password.LdapShaPasswordEncoder());
        encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
        encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
        encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
        encoders.put("scrypt", new SCryptPasswordEncoder());
        encoders.put("SHA-1", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-1"));
        encoders.put("SHA-256", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("SHA-256"));
        encoders.put("sha256", new org.springframework.security.crypto.password.StandardPasswordEncoder());

使用了预制算法

可以将数据库中密码更新为 {算法前缀}原密码 ,来进行升级。新增数据时,也要加入前缀。

原来未使用加密算法

可以将数据库中密码更新为 {noop}原密码 ,来进行升级。新增数据时,也要加入前缀 noop

自定义的算法

在升级前,我们是可以如下定义的:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder
                .userDetailsService(userDetailsService);
                .passwordEncoder(passwordEncoder());
    }
    
    private PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                // 自定义加密算法
                return (String) rawPassword;
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                // 自定义匹配算法
                return rawPassword.equals(encodedPassword);
            }
        };
    }
}

升级后,沿用上述写法会报错:

Error:(49, 17) java: 无法访问org.springframework.security.authentication.encoding.PasswordEncoder
  找不到org.springframework.security.authentication.encoding.PasswordEncoder的类文件

改写为:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.authenticationProvider(authProvider());

    }
    
    private DaoAuthenticationProvider authProvider() {
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(userDetailsService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }    
    
    private PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return (String) rawPassword;
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return rawPassword.equals(encodedPassword);
            }
        };
    }
}

注意:直接使用 官方文档推荐 的方法并不生效,猜想原因应该是升级前版本不匹配。官方文档说 If you are migrating from Spring Security 4.2.x you can revert to the previous behavior by exposing a NoOpPasswordEncoder bean. ,我的只所以没有生效,应该是前版本不是4.2.x,在此未做验证。

新项目

新项目,在进行密码匹配时,会根据前缀自动调用密码匹配算法。所以,我们只需要在保存用户时,为其调用合适的算法,并设置相应的前缀即可。在此,建立直接调用官方的:

String 加密后的密码 = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("原密码");
原文  https://segmentfault.com/a/1190000020103547
正文到此结束
Loading...