簡體   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