Spring Security 5:没有为id" null"映射的PasswordEncoder;

时间:2018-04-04 14:51:53

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

我正在从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);
    }
}

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

8 个答案:

答案 0 :(得分:47)

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

.secret("{noop}secret")

答案 1 :(得分:7)

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

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

以下使用的方法已弃用。

这是我从以下地方获得的:

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

答案 2 :(得分:2)

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

例如:

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

答案 3 :(得分:2)

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

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

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

Id示例为:

{ bcrypt } $ 2a $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG {noop}密码 { pbkdf2 } 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc { scrypt } $ e0801 $ 8bWJaSu2IKSn9Z9kM + TPXfOc / 9bdYSrN1oD9qfVThWEwdRTnO7re7Ei + fUZRJ68k9lTyuTeUp4of4g24hHnzu == $ OAOEC05 + BXXYY1Y6YQ6 { sha256 } 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

答案 6 :(得分:0)

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

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

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

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

希望这会有所帮助。

答案 7 :(得分:-1)

关于

  

编码密码看起来不像BCrypt

在我的情况下,默认构造函数(10)使用的BCryptPasswordEncoder强度不匹配,因为pwd哈希是使用强度4生成的。因此,我将强度设置为显式。

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

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