Keamanan Musim Semi 5 : Tidak ada PasswordEncoder yang dipetakan untuk id null

Saya bermigrasi dari Spring Boot 1.4.9 ke Spring Boot 2.0 dan juga ke Spring Security 5 dan saya mencoba melakukan otentikasi melalui OAuth 2. Tetapi saya mendapatkan kesalahan ini:

java.lang.IllegalArgumentException: Tidak ada PasswordEncoder yang dipetakan untuk id "null

Dari dokumentasi Spring Keamanan 5, Saya mengetahui bahwa format penyimpanan untuk kata sandi diubah.

Dalam kode saya saat ini, saya telah membuat kacang encoder kata sandi sebagai:

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

Namun itu memberi saya kesalahan di bawah ini:

Sandi yang dikodekan tidak terlihat seperti BCrypt

Jadi saya memperbarui encoder sesuai Spring Security 5 ke:

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

Sekarang jika saya dapat melihat kata sandi di database, ia disimpan sebagai

{bcrypt}$2a$10$LoV/3z36G86x6Gn101aekuz3q9d7yfBp3jFn7dzNN/AL5630FyUQ

Dengan hilangnya kesalahan pertama dan sekarang ketika saya mencoba melakukan otentikasi saya mendapatkan kesalahan di bawah ini:

java.lang.IllegalArgumentException: Tidak ada PasswordEncoder yang dipetakan untuk id "null

Untuk mengatasi masalah ini saya mencoba semua pertanyaan di bawah ini dari Stackoverflow:

Ini pertanyaan yang mirip dengan saya tetapi belum terjawab:

CATATAN: Saya sudah menyimpan kata sandi terenkripsi di database jadi tidak perlu menyandikan lagi di UserDetailsService.

Di Keamanan musim semi 5 dokumentasi mereka menyarankan Anda dapat menangani pengecualian ini menggunakan:

MendelegasikanPasswordEncoder.setDefaultPasswordEncoderForMatches(PasswordEncoder)

Jika ini perbaikannya, lalu di mana saya harus meletakkannya? Saya telah mencoba memasukkannya ke dalam PasswordEncoder bean seperti di bawah ini tetapi tidak berhasil:

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

Kelas Keamanan Web Saya

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

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

Tolong bimbing saya dengan masalah ini. Saya telah menghabiskan waktu berjam-jam untuk memperbaikinya tetapi tidak dapat memperbaikinya.


person Jimmy    schedule 04.04.2018    source sumber
comment
Saya sedikit lebih deskriptif mengenai masalah ini. Ketika saya beralih dari keamanan Spring 4 ke 5. Saya mendapatkan kesalahan pertama dan kemudian saya menyelesaikannya dengan mengubah pembuat kata sandi saya, Ini mulai memberi saya kesalahan kedua. Dan pesan kesalahannya berbeda. 1) Kata sandi yang disandikan tidak terlihat seperti BCrypt dan 2) java.lang.IllegalArgumentException: Tidak ada PasswordEncoder yang dipetakan untuk id null. Masalah kedua yang saya alami saat ini.   -  person Jimmy    schedule 05.04.2018
comment
Saya yakin masalahnya ada pada klien (bukan akun pengguna individu). Saya pribadi sudah menyandikan detail pengguna, tetapi belum mengkodekan klien. Sekarang, pengguna OAuth2 diharapkan mengkodekan rahasia klien (serta sandi pengguna). Secara khusus, setel passwordEncoder pada ClientDetailsServiceConfigurer atau awali rahasianya dengan {noop}. Harapan itu masuk akal dan membantu seseorang.   -  person KellyM    schedule 06.04.2018
comment
Masalah ini diselesaikan untuk saya dengan menjalankan mvn clean package. Pasti ada masalah dengan cache.   -  person Janac Meena    schedule 26.02.2021


Jawaban (8)


Saat Anda mengonfigurasi ClientDetailsServiceConfigurer, Anda juga harus menerapkan format penyimpanan kata sandi ke rahasia klien.

.secret("{noop}secret")
person Edwin Diaz    schedule 06.04.2018
comment
lihat juga Pencocokan Kata Sandi dan Format Penyimpanan Kata Sandi di Spring Security 5.0.0.RC1 Pernyataan yang dirilis - person olivmir; 09.04.2018
comment
juga dapat menambahkan secret(passwordEncoder.encode("secret")) dengan menggunakan encoder kata sandi default. - person Troy Young; 04.01.2019
comment
bagaimana jika saya tidak menggunakan rahasia? - person f.khantsis; 10.02.2019
comment
juga: auth.inMemoryAuthentication() .withUser(admin).roles(ADMIN).password({noop}password); - person bzhu; 01.09.2020

Tambahkan .password("{noop}password") ke file konfigurasi Keamanan.

Misalnya :

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");
person Sailokesh Aithagoni    schedule 07.06.2019
comment
dapatkah Anda memberi tahu saya alasan Anda menambahkan {noop}? - person Indrajeet Gour; 12.08.2019
comment
Hanya ingin menggunakan kata sandi biasa...! Sepertinya tidak ada operasi yang dilakukan untuk itu! Saya kira demikian! ;) - person Sailokesh Aithagoni; 16.08.2019

Bagi siapa pun yang menghadapi masalah yang sama dan tidak membutuhkan solusi aman - terutama untuk pengujian dan debugging - di memori pengguna masih dapat dikonfigurasi.

Ini hanya untuk bermain-main - bukan skenario dunia nyata.

Pendekatan yang digunakan di bawah ini tidak digunakan lagi.

Dari sinilah saya mendapatkannya:


Dalam WebSecurityConfigurerAdapter Anda tambahkan yang berikut ini:

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

Di sini, tentu saja, kata sandi di-hash, tetapi masih tersedia di memori.


Tentu saja, Anda juga dapat menggunakan PasswordEncoder asli seperti BCryptPasswordEncoder dan mengawali kata sandi dengan id yang benar:

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
person rocksteady    schedule 14.04.2018
comment
Hai, sebenarnya kami tidak bisa menggunakan NoOpPasswordEncoder. Karena sudah tidak digunakan lagi dalam versi keamanan pegas baru. Dan sesuai dengan dokumentasi keamanan pegas (spring.io/blog/2017/11/01/) meskipun kita menggunakan NoOpPasswordEncoder kita harus menambahkan {noop} sebagai id pada kata sandi dan rahasia. Saya yakin ini tidak akan membenarkan pertanyaan saya. Masalah utama dengan semua solusinya adalah mereka tidak menyebutkan bahwa Anda perlu menambahkan ID ke rahasia Anda juga. - person Jimmy; 15.04.2018
comment
Ya, itu sudah tidak digunakan lagi. Sumber saya juga mengatakan demikian. Jika Anda hanya menggunakan NoOpPasswordEncoder - tanpa BCryptPasswordEncoder - itu berhasil. Saya menggunakan boot musim semi 2.0.1.RELEASE. - person rocksteady; 15.04.2018
comment
Tapi, seperti yang saya sebutkan di jawaban, ini bukan skenario produksi sama sekali. - person rocksteady; 15.04.2018
comment
Saya tidak mengerti mengapa kita harus menggunakan kelas yang tidak digunakan lagi bahkan untuk tujuan Pengujian? - person Jimmy; 16.04.2018
comment
Oke, Anda sepenuhnya benar. Saya menambahkan jawaban ini untuk setiap orang yang hanya bermain-main dan mulai mendalami masalah ini menggunakan tutorial yang sudah ketinggalan zaman, mungkin. Itu sebabnya saya juga menyebutkan BCryptPasswordEncoder. Saya memperbarui jawaban saya. - person rocksteady; 16.04.2018
comment
Jawabannya membantu saya dengan skenario pengujian, terima kasih - person Osama Abdulsattar; 15.01.2019
comment
Itu sebabnya saya mengatakannya di awal jawaban. - person rocksteady; 04.02.2019

Tidak tahu apakah ini akan membantu siapa pun. Kode WebSecurityConfigurer dan OAuth2Config saya yang berfungsi seperti di bawah ini:

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

Konfigurasi Keamanan Web:

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

}

Berikut ini tautan ke proyek tersebut: springboot-authorization-server-oauth2

person CrownWangGuan    schedule 21.01.2020

Setiap kali Spring menyimpan kata sandi, ia menempatkan awalan encoder dalam kata sandi yang disandikan seperti bcrypt, scrypt, pbkdf2 dll. sehingga ketika tiba waktunya untuk memecahkan kode kata sandi, ia dapat menggunakan encoder yang sesuai untuk memecahkan kode. jika tidak ada awalan dalam kata sandi yang disandikan, ia menggunakan defaultPasswordEncoderForMatches. Anda dapat melihat metode kecocokan DelegatingPasswordEncoder.class untuk melihat cara kerjanya. jadi pada dasarnya kita perlu menyetel defaultPasswordEncoderForMatches dengan baris berikut.

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

Sekarang, Anda mungkin juga harus memberikan pembuat enkode ini dengan DefaultPasswordEncoderForMatches ke penyedia autentikasi Anda juga. Saya melakukannya dengan baris di bawah ini di kelas konfigurasi saya.

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

Jika Anda mengambil nama pengguna dan kata sandi dari database, Anda dapat menggunakan kode di bawah ini untuk menambahkan instance NoOpPassword.

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

Di mana adm adalah objek pengguna khusus untuk proyek saya yang memiliki metode getPassword() dan getUsername().

Ingat juga, untuk membuat POJO Pengguna khusus, Anda harus mengimplementasikan antarmuka UserDetails dan mengimplementasikan semua metodenya.

Semoga ini membantu.

person Ashish Singh    schedule 16.05.2020

Anda dapat membaca di Dokumentasi Keamanan Musim Semi resmi bahwa untuk DelegatingPasswordEncoder format umum sandi adalah: {id}encodedPassword

Sedemikian rupa sehingga id adalah pengidentifikasi yang digunakan untuk mencari PasswordEncoder mana yang harus digunakan dan encodedPassword adalah kata sandi asli yang disandikan untuk PasswordEncoder yang dipilih. Id harus di awal kata sandi, diawali dengan { dan diakhiri dengan }. Jika id tidak dapat ditemukan, id akan menjadi null. Misalnya, berikut ini mungkin daftar kata sandi yang dikodekan menggunakan id berbeda. Semua kata sandi asli adalah "kata sandi".

Contoh identitasnya adalah:

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

person Continuity8    schedule 07.05.2020
comment
Masalah itu karena kita juga perlu memiliki tipe enkripsi dalam rahasia klien. Sampai sekarang kata sandi sudah menambahkan jenis enkripsi pada kata sandi terenkripsi. - person Jimmy; 08.05.2020

Tentang

Kata sandi yang disandikan tidak terlihat seperti BCrypt

Dalam kasus saya, ada ketidakcocokan dalam kekuatan BCryptPasswordEncoder yang digunakan oleh konstruktor default (10) karena hash pwd dihasilkan dengan kekuatan 4. Jadi saya telah menetapkan kekuatan secara eksplisit.

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

juga versi Spring Security saya adalah 5.1.6 dan berfungsi sempurna dengan BCryptPasswordEncoder

person Bender    schedule 13.08.2019