Java Spring Security 5:没有为 id“null”映射的 PasswordEncoder

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/49654143/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 03:04:55  来源:igfitidea点击:

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

javaspringspring-bootspring-securityspring-security-oauth2

提问by Jimmy

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:

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

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

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

From the documentation of Spring Security 5, I get to know that storage format for password is changed.

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

In my current code I have created my password encoder bean as:

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

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

However it was giving me below error:

但是它给了我以下错误:

Encoded password does not look like BCrypt

编码密码看起来不像 BCrypt

So I update the encoder as per the Spring Security 5document to:

因此,我根据Spring Security 5文档将编码器更新为:

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

Now if I can see password in database it is storing as

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

{bcrypt}a$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ

With that 1st error gone and now when I am trying to do authentication I am getting below error:

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

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

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

To solve this issue I tried all the below questions from Stackoverflow:

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

Here is a question similar to mine but not answerd:

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

NOTE: I am already storing encrypted password in database so no need to encode again in UserDetailsService.

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

In the Spring security 5documentation they suggested you can handle this exception using:

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

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

If this is the fix then where should I put it? I have tried to put it in PasswordEncoderbean like below but it wasn't working:

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

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

MyWebSecurity class

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

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

Please guide me with this issue. I have spend hours to fix this but not able to fix.

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

采纳答案by Edwin Diaz-Mendez

When you are configuring the ClientDetailsServiceConfigurer, you have to also apply the new password storage formatto the client secret.

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

.secret("{noop}secret")

回答by rocksteady

For anyone facing the same issue and not in need of a secure solution - for testing and debugging mainly - in memory users can still be configured.

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

This is just for playing around - no real world scenario.

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

The approach used below is deprecated.

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

This is where I got it from:

这是我从哪里得到的:



Within your WebSecurityConfigurerAdapteradd the following:

在您的WebSecurityConfigurerAdapter添加以下内容:

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

Here, obviously, passwords are hashed, but still are available in memory.

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



Of course, you could also use a real PasswordEncoderlike BCryptPasswordEncoderand prefix the password with the correct id:

当然,您也可以使用真正的PasswordEncoderlikeBCryptPasswordEncoder并在密码前加上正确的id:

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

回答by Sailokesh Aithagoni

Add .password("{noop}password")to Security config file.

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

For example :

例如 :

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

回答by Bender

Regarding

关于

Encoded password does not look like BCrypt

编码密码看起来不像 BCrypt

In my case there was a mismatch in BCryptPasswordEncoder strength used by default constructor (10) as pwd hash was generated with strength 4. So I've set strength explicit.

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

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

also my Spring Security version is 5.1.6 and it perfectly works with BCryptPasswordEncoder

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

回答by Vikky

Whenever Spring stores the password, it puts a prefix of encoder in the encoded passwords like bcrypt, scrypt, pbkdf2 etc. so that when it is time to decode the password, it can use appropriate encoder to decode. if there is no prefix in the encoded password it uses defaultPasswordEncoderForMatches. You can view DelegatingPasswordEncoder.class's matches method to see how it works. so basically we need to set defaultPasswordEncoderForMatches by the following lines.

每当 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;      
}

Now, you might also have to provide this encoder with DefaultPasswordEncoderForMatches to your authentication provider also. I did that with below lines in my config classes.

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

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

回答by CrownWangGuan

Don't know if this will help anyone. My working WebSecurityConfigurer and OAuth2Config code as below:

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

OAuth2Config File:

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

}

Here is the link to the project: springboot-authorization-server-oauth2

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

回答by Continuity8

You can read in the official Spring Security Documentationthat for the DelegatingPasswordEncoderthe general format for a password is: {id}encodedPassword

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

Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".

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

Id examples are:

ID 示例是:

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

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

回答by Ashish Singh

If you are fetching the username and password from the database, you can use below code to add NoOpPassword instance.

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

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

Where admis a custom user object for my project which has getPassword() and getUsername() methods.

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

Also remember, to make a custom User POJO, you'll have to implement UserDetails interface and implements all of it's methods.

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

Hope this helps.

希望这可以帮助。