繁体   English   中英

Spring 安全性 5:没有为 id“null”映射 PasswordEncoder

[英]Spring Security 5 : There is no PasswordEncoder mapped for the id "null"

I am migrating from Spring Boot 1.4.9 to Spring Boot 2.0 and also to Spring Security 5 and I am trying to do authenticate via OAuth 2. But I am getting this error:

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 安全 5文档中,他们建议您可以使用以下方法处理此异常:

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

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

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

MyWebSecurity class

@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);
    }
}

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

在配置ClientDetailsServiceConfigurer ,您还必须将新密码存储格式应用于客户端密钥。

.secret("{noop}secret")

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

例如:

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

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

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

下面使用的方法已被弃用。

这是我从哪里得到的:


在您的WebSecurityConfigurerAdapter添加以下内容:

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

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


当然,你也可以使用像BCryptPasswordEncoder这样的真正的PasswordEncoder并在密码前加上正确的 id:

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

不知道这是否会帮助任何人。 我的工作 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);
    }
}

网络安全配置器:

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

每当 Spring 存储密码时,它都会在编码后的密码(如 bcrypt、scrypt、pbkdf2 等)中放置一个编码器前缀,以便在需要解码密码时,可以使用合适的编码器进行解码。 如果编码密码中没有前缀,则它使用 defaultPasswordEncoderForMatches。 您可以查看 DelegatingPasswordEncoder.class 的匹配方法以了解其工作原理。 所以基本上我们需要通过以下几行设置 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;
    }

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

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

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

还要记住,要创建自定义用户 POJO,您必须实现 UserDetails 接口并实现它的所有方法。

希望这会有所帮助。

您可以在官方 Spring Security 文档中阅读,对于DelegatingPasswordEncoder ,密码的一般格式为: {id}encodedPassword

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

ID 示例是:

{bcrypt} $ 2A $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG {空操作}密码{PBKDF2} 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {scrypt} $ $ e0801 + 8bWJaSu2IKSn9Z9kM TPXfOc / 9bdYSrN1oD9qfVThWEwdRTnO7re7Ei + fUZRJ68k9lTyuTeUp4of4g24hHnazw == $ OAOec05 + bXxvuu / 1qZ6NUR + xQYvYv7BeL1QxwRpY5Pc =
{ sha256 }97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

关于

编码密码看起来不像 BCrypt

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

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

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

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

Spring Boot 官方文档已经提供了解决这个问题

解决错误的最简单方法是切换到显式提供密码编码器的密码编码器。 解决它的最简单方法是弄清楚您的密码当前是如何存储的,并明确提供正确的 PasswordEncoder。

如果您从 Spring Security 4.2.x 迁移,您可以通过公开 NoOpPasswordEncoder bean 恢复到以前的行为。

或者,您可以为所有密码添加正确的 id 前缀,然后继续使用 DelegatingPasswordEncoder。 例如,如果您使用 BCrypt,您将迁移... 更多

@Configuration
@EnableWebSecurity
public class AppSecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService;
    
    
    @Bean
    public AuthenticationProvider authProvider() {
        
        DaoAuthenticationProvider provider =  new DaoAuthenticationProvider();
        provider.setUserDetailsService(userDetailsService);
        provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance());
        
        return provider;
    }

}

添加以下两个注释可以解决该问题
@Configuration @EnableWebSecurity

发生此错误可能是您错误地忘记对业务逻辑使用注释“@Bean”。

  @Bean 
  public AuthenticationProvider authProvider() {
      DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
      provider.setUserDetailsService(userDetailsService);
      provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); 
      return provider; 
  }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM