Spring 安全性 5:没有为 id 映射的密码编码器"null"



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

java.lang.IlgalArgumentException:没有密码编码器用于ID" null

(

从Spring Security 5的文档中,我知道更改了密码的存储格式。

在我当前的代码中,我创建了密码编码bean as:

@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.IlgalArgumentException:没有密码编码器用于ID" null

(

要解决此问题,我尝试了以下所有问题,请从Stackoverflow:

  • 春季启动密码编码器错误

  • Spring Oauth2。密码编码器未在DaoAuthentication Provider中设置

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

  • 春季安全5-密码迁移

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

在Spring Security 5文档中,他们建议您使用以下方式处理此例外:

如果这是修复程序,那我应该把它放在哪里?我试图将其放入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);
    }
}

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

当您配置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();
}

在这里显然,密码已被哈希,但仍在内存中可用。


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

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

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"错误消息从弹簧安全性4升级到5。/div>

每当春季存储密码时,它都会将编码器的前缀放在编码的密码中,例如bcrypt,scrypt,pbkdf2等可以使用合适的编码器来解码。如果编码密码中没有前缀,则使用DefaultPasswordEncoderFormatches。您可以查看委托人wordencoder.class的匹配方法,以查看其工作原理。因此,基本上,我们需要通过以下行设置DefaultPasswordencoderFormatch。

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

现在,您可能还必须向您的身份验证提供商提供此编码器。我在配置类中使用以下行进行了此操作。

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

不知道这是否会对任何人有所帮助。我的工作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-osterorization-server-oauth2

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

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

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

还记得,要制作自定义的用户POJO,您必须实现用户访问界面并实现所有方法。

希望这会有所帮助。

您可以在官方的弹簧安全文档中阅读,即DelegatingPasswordEncoder密码的一般格式为: {ID} encodedPassword Word

使ID是用于查找的标识符,应使用哪个密码编码器,并且编码PasseDword是所选密码登录编码器的原始编码密码。ID必须在密码的开头,以{并以}结尾开始。如果找不到ID,则ID为null 。例如,以下可能是使用不同ID编码的密码列表。所有原始密码都是"密码"。

ID示例是:

{ bcrypt } $ 2a $ 10 $ dxj3swsw6g7p50lgmmkkmkekmke.20cqqubk3.hzwzg3yb1tlry.fqvm/bg {noop}密码 { PBKDF2 } 5D923B44A6D129F3DDF3DDF3E3C8C8D29412723DCBDE72445E72445E8EF6BF3BF3B508FB508FBFAR { SHA256 } 97CDE38028AD898EBC02E690819FA220E888C62E06999403E9403E94FFFFFF291CFFFFAF8410849F27605ABCBC0

用于XML配置。

<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="admin" password="{noop}1234" authorities="ROLE_ADMIN"/>
        </user-service>
    </authentication-provider>
</authentication-manager>

Spring Boot官方文档为此提供了解决方案

解决错误的最简单方法是切换到明确提供密码编码的密码编码器。解决它的最简单方法是找出当前如何存储密码并明确提供正确的密码编码器。

如果您从弹簧安全4.2.x迁移,可以通过公开nooppasswordencoder bean恢复为先前的行为。

另外,您可以将所有密码带有正确的ID前缀,并继续使用委托passwordencoder。例如,如果您使用的是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

juste将此bean添加到带注释的@configuration类中。

@Configuration
public class BootConfiguration {
    @Bean
    @Primary
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
}

接下来,转到您的Webscurity配置类,然后调用密码bean将其添加到身份验证提供程序中,请勿忘记在您的auth manager bean中设置了身份验证提供商bean,并最终设置了SecurityFilterChain中的身份验证管理器Bean,请使用下面的示例

    @EnableWebSecurity
    @Configuration
    public class DefaultSecurityConfig {
    
        @Autowire
        private PasswordEncoder passwordEncoder
        
    
        @Bean
        public DaoAuthenticationProvider authenticationProvider() {
                DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
                authenticationProvider.setUserDetailsService(userManager);
                authenticationProvider.setPasswordEncoder(passwordEncoder);
                return authenticationProvider;
            }
       @Bean
        public AuthenticationManager authManager(HttpSecurity http) throws Exception {
            AuthenticationManagerBuilder authenticationManagerBuilder = http.getSharedObject(AuthenticationManagerBuilder.class); 
authenticationManagerBuilder.authenticationProvider(authenticationProvider();
        return authenticationManagerBuilder.build();
    }

 @Bean
 SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(authorize -> authorize
          
            .anyRequest().authenticated());
    http.authenticationManager(authManager(http));
    http.exceptionHandling(exceptions->exceptions
            .accessDeniedHandler(accessDeniedHandler));
    http.formLogin(formLogin-> formLogin.loginPage("/login")
            .permitAll());
    http.logout(logout-> logout
            .permitAll()
    return http.build();
}

}

最新更新