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

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

  1. 获取密码
  2. 获取默认加密、密码匹配对象

1.1 获取获取的加密类型(加密前缀)
1.2 根据类型找算法
1.3 没找到算法,则调用默认算法
1.4 默认算法代码如上,抛出异常:

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

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

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

  1. 获取到了密码,比如为123456.
  2. 获取默认加密、密码匹配对象:DelegatingPasswordEncoder

1.1 spring尝试从123456中,获取一个加密前缀的东西。但获取的值为null。
1.2 没有找到算法,则调用默认算法,此时默认对象为:UnmappedIdPasswordEncoder
1.3 运行对象UnmappedIdPasswordEncodermatches算法
1.4 抛出异常。

解决问题

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.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,在此未做验证。

注意:在升级前的定义方法已失效,比如:

@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的类文件

新项目

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

String 加密后的密码 = PasswordEncoderFactories.createDelegatingPasswordEncoder().encode("原密码");

潘杰
3.1k 声望238 粉丝