【问题标题】:Spring Security 5 : There is no PasswordEncoder mapped for the id "null"Spring Security 5:没有为 id“null”映射 PasswordEncoder
【发布时间】:2018-09-14 04:48:57
【问题描述】:

我正在从 Spring Boot 1.4.9 迁移到 Spring Boot 2.0 以及 Spring Security 5,我正在尝试通过 OAuth 2 进行身份验证。但是我收到了这个错误:

java.lang.IllegalArgumentException:没有为 id “null”映射 PasswordEncoder

Spring Security 5 的文档中,我了解到 密码的存储格式已更改。

在我当前的代码中,我将密码编码器 bean 创建为:

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

但是它给了我以下错误:

编码密码看起来不像 BCrypt

所以我根据Spring Security 5 文档将编码器更新为:

@Bean
public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}

现在,如果我可以在数据库中看到密码,它将存储为

{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ

第一个错误消失了,现在当我尝试进行身份验证时出现以下错误:

java.lang.IllegalArgumentException:没有为 id “null”映射 PasswordEncoder

为了解决这个问题,我尝试了 Stackoverflow 中的以下所有问题:

这是一个与我类似但没有回答的问题:

注意:我已经将加密密码存储在数据库中,因此无需在 UserDetailsService 中再次编码。

Spring security 5 文档中,他们建议您可以使用以下方法处理此异常:

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

如果这是修复,那么我应该把它放在哪里?我试图把它放在PasswordEncoder bean 中,如下所示,但它不起作用:

DelegatingPasswordEncoder def = new DelegatingPasswordEncoder(idForEncode, encoders);
def.setDefaultPasswordEncoderForMatches(passwordEncoder);

MyWebSecurity 类

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {

        web
                .ignoring()
                .antMatchers(HttpMethod.OPTIONS)
                .antMatchers("/api/user/add");
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

MyOauth2 配置

@Configuration
@EnableAuthorizationServer
protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;


    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

    @Bean
    public DefaultAccessTokenConverter accessTokenConverter() {
        return new DefaultAccessTokenConverter();
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints)
            throws Exception {
        endpoints
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancer())
                .accessTokenConverter(accessTokenConverter())
                .authenticationManager(authenticationManager);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient("test")
                .scopes("read", "write")
                .authorities(Roles.ADMIN.name(), Roles.USER.name())
                .authorizedGrantTypes("password", "refresh_token")
                .secret("secret")
                .accessTokenValiditySeconds(1800);
    }
}

请指导我解决这个问题。我花了几个小时来解决这个问题,但无法解决。

【问题讨论】:

  • 我对这个问题的描述性更强。当我从 Spring security 4 切换到 5 时。我遇到了第一个错误,然后我通过更改密码生成器解决了它,它开始给我第二个错误。并且错误消息是不同的。 1)编码密码看起来不像 BCrypt 和 2)java.lang.IllegalArgumentException:没有为 id “null”映射 PasswordEncoder。我目前遇到的第二个问题。
  • 我认为问题出在客户端(而不是个人用户帐户)。我个人已经对用户详细信息进行了编码,但没有对客户端进行编码。现在,OAuth2 的用户需要对 client 密码(以及用户密码)进行编码。具体来说,要么在ClientDetailsServiceConfigurer 上设置passwordEncoder,要么在密钥前面加上{noop}。希望这是有道理的,可以帮助别人。
  • 运行mvn clean package 为我解决了这个问题。一定是缓存有问题。

标签: java spring spring-boot spring-security spring-security-oauth2


【解决方案1】:

当您配置ClientDetailsServiceConfigurer 时,您还必须将新的password storage format 应用于客户端密码。

.secret("{noop}secret")

【讨论】:

  • 另见 Password MatchingPassword Storage Format 在 Spring Security 5.0.0.RC1 发布的备注
  • 也可以使用默认密码编码器添加secret(passwordEncoder.encode("secret"))
  • 如果我不使用秘密怎么办?
  • 还有:auth.inMemoryAuthentication() .withUser("admin").roles("ADMIN").password("{noop}password");
  • @bzhu 如果我不想硬编码我不知道的未来用户会注册怎么办?
【解决方案2】:

对于面临相同问题且不需要安全解决方案(主要用于测试和调试)的任何人,仍然可以配置内存用户。

这只是为了玩 - 不是真实世界的场景。

不推荐使用下面使用的方法。

这是我从这里得到的:


在您的 WebSecurityConfigurerAdapter 中添加以下内容:

@SuppressWarnings("deprecation")
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}

显然,这里的密码是经过哈希处理的,但仍然可以在内存中使用。


当然,您也可以使用真正的PasswordEncoder,例如BCryptPasswordEncoder,并在密码前加上正确的ID:

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

【讨论】:

  • 嗨,实际上我们不能使用 NoOpPasswordEncoder。因为它在新的 spring-security 版本中已被弃用。根据 spring 安全文档 (spring.io/blog/2017/11/01/…),即使我们使用 NoOpPasswordEncoder,我们也必须将 {noop} 作为 id 添加到密码和密码中。我相信这不能证明我的问题是正确的。所有解决方案的主要问题是他们没有提到您也需要为您的密钥添加一个 ID。
  • 是的,它已被弃用。我的消息来源也这么说。如果您只使用NoOpPasswordEncoder - 没有BCryptPasswordEncoder - 它可以工作。我用弹簧靴2.0.1.RELEASE
  • 但是,正如我在答案中提到的,这根本不是生产场景。
  • 我不明白为什么我们应该使用已弃用的类,即使是为了测试目的?
  • 答案帮助我完成了测试场景,谢谢
【解决方案3】:

.password("{noop}password") 添加到安全配置文件中。

例如:

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");

【讨论】:

  • 你能告诉我你为什么添加 {noop} 吗?
  • 只想使用普通密码...!就像没有对它执行的操作一样!我想是这样! ;)
【解决方案4】:

关于

编码密码看起来不像 BCrypt

在我的例子中,默认构造函数 (10) 使用的 BCryptPasswordEncoder 强度不匹配,因为 pwd 哈希是使用强度 4 生成的。所以我已经明确设置了强度。

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

我的 Spring Security 版本也是 5.1.6,它与 BCryptPasswordEncoder 完美配合

【讨论】:

    【解决方案5】:

    每当 Spring 存储密码时,它都会在编码后的密码(如 bcrypt、scrypt、pbkdf2 等)中添加编码器前缀,以便在对密码进行解码时,可以使用适当的编码器进行解码。如果编码密码中没有前缀,则使用 defaultPasswordEncoderForMatches。您可以查看 DelegatingPasswordEncoder.class 的 matches 方法以了解它是如何工作的。所以基本上我们需要通过以下几行设置 defaultPasswordEncoderForMatches。

    @Bean(name="myPasswordEncoder")
    public PasswordEncoder getPasswordEncoder() {
            DelegatingPasswordEncoder delPasswordEncoder=  (DelegatingPasswordEncoder)PasswordEncoderFactories.createDelegatingPasswordEncoder();
            BCryptPasswordEncoder bcryptPasswordEncoder =new BCryptPasswordEncoder();
        delPasswordEncoder.setDefaultPasswordEncoderForMatches(bcryptPasswordEncoder);
        return delPasswordEncoder;      
    }
    

    现在,您可能还需要将此编码器与 DefaultPasswordEncoderForMatches 一起提供给您的身份验证提供程序。我在我的配置类中使用以下几行来做到这一点。

    @Bean
        @Autowired  
        public DaoAuthenticationProvider getDaoAuthenticationProvider(@Qualifier("myPasswordEncoder") PasswordEncoder passwordEncoder, UserDetailsService userDetailsServiceJDBC) {
            DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
            daoAuthenticationProvider.setPasswordEncoder(passwordEncoder);
            daoAuthenticationProvider.setUserDetailsService(userDetailsServiceJDBC);
            return daoAuthenticationProvider;
        }
    

    【讨论】:

      【解决方案6】:

      不知道这是否会帮助任何人。我的工作 WebSecurityConfigurer 和 OAuth2Config 代码如下:

      OAuth2Config 文件:

      package com.crown.AuthenticationServer.security;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.authentication.AuthenticationManager;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
      import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
      import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
      
      @Configuration
      public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
      
          @Autowired
          private AuthenticationManager authenticationManager;
      
          @Autowired
          private UserDetailsService userDetailsService;
      
          @Override
          public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
              clients.inMemory()
                  .withClient("crown")
                  .secret("{noop}thisissecret")
                  .authorizedGrantTypes("refresh_token", "password", "client_credentials")
                  .scopes("webclient", "mobileclient");
          }
      
          @Override
          public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
              endpoints
                  .authenticationManager(authenticationManager)
                  .userDetailsService(userDetailsService);
          }
      }
      

      WebSecurityConfigurer:

      package com.crown.AuthenticationServer.security;
      
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.authentication.AuthenticationManager;
      import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
      import org.springframework.security.core.userdetails.User;
      import org.springframework.security.core.userdetails.UserDetails;
      import org.springframework.security.core.userdetails.UserDetailsService;
      import org.springframework.security.crypto.factory.PasswordEncoderFactories;
      import org.springframework.security.crypto.password.PasswordEncoder;
      import org.springframework.security.provisioning.InMemoryUserDetailsManager;
      
      
      @Configuration
      public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
      
          @Override
          @Bean
          public AuthenticationManager authenticationManagerBean() throws Exception {
              return super.authenticationManagerBean();
          }
      
          @Bean
          @Override
          public UserDetailsService userDetailsService() {
      
              PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
      
              final User.UserBuilder userBuilder = User.builder().passwordEncoder(encoder::encode);
              UserDetails user = userBuilder
                  .username("john.carnell")
                  .password("password")
                  .roles("USER")
                  .build();
      
              UserDetails admin = userBuilder
                  .username("william.woodward")
                  .password("password")
                  .roles("USER","ADMIN")
                  .build();
      
              return new InMemoryUserDetailsManager(user, admin);
          }
      
      }
      

      这里是项目的链接: springboot-authorization-server-oauth2

      【讨论】:

        【解决方案7】:

        您可以阅读in the official Spring Security Documentation,对于DelegatingPasswordEncoder,密码的一般格式为:{id}encodedPassword

        这样 id 是用于查找应该使用哪个 PasswordEncoder 的标识符,encodedPassword 是所选 PasswordEncoder 的原始编码密码。 id 必须在密码的开头,以 { 开头,以 } 结尾。 如果找不到 id,则 id 将为空。例如,以下可能是使用不同 id 编码的密码列表。所有原始密码都是“密码”。

        Id 示例是:

        {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}密码 {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {加密}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRp
        {sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

        【讨论】:

        • 这个问题是因为我们还需要在客户端密码中设置加密类型。截至目前,密码已经在加密密码上附加了加密类型。
        • 正确链接到documentation
        【解决方案8】:

        如果您从数据库中获取用户名和密码, 您可以使用以下代码添加 NoOpPassword 实例。

        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
           auth.userDetailsService(adm).passwordEncoder(NoOpPasswordEncoder.getInstance());
        }
        

        adm 是我的项目的自定义用户对象,它具有 getPassword() 和 getUsername() 方法。

        还请记住,要制作自定义 User POJO,您必须实现 UserDetails 接口并实现它的所有方法。

        希望这会有所帮助。

        【讨论】:

          【解决方案9】:

          从 Spring Security 4 升级到 5 时出现java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null" 错误消息。请参阅此Baeldung article 以获得完整的解释和可能的解决方案。

          【讨论】:

            猜你喜欢
            • 2020-10-27
            • 2020-08-11
            • 2020-07-03
            • 2018-10-30
            • 2019-06-02
            • 2019-08-28
            • 2021-06-02
            • 2018-09-11
            相关资源
            最近更新 更多