Spring Security 5: ไม่มีการแมป PasswordEncoder สำหรับ id null

ฉันกำลังย้ายจาก Spring Boot 1.4.9 เป็น Spring Boot 2.0 และไปยัง Spring Security 5 และฉันกำลังพยายามตรวจสอบสิทธิ์ผ่าน OAuth 2 แต่ฉันได้รับข้อผิดพลาดนี้:

java.lang.IllegalArgumentException: ไม่มี PasswordEncoder ที่แมปสำหรับ id "null

จากเอกสารประกอบของ Spring ความปลอดภัย 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: ไม่มี PasswordEncoder ที่แมปสำหรับ id "null

เพื่อแก้ไขปัญหานี้ ฉันลองคำถามด้านล่างทั้งหมดจาก Stackoverflow:

นี่เป็นคำถามที่คล้ายกับของฉันแต่ไม่ได้รับคำตอบ:

หมายเหตุ: ฉันกำลังจัดเก็บรหัสผ่านที่เข้ารหัสไว้ในฐานข้อมูลอยู่แล้ว ดังนั้นจึงไม่จำเป็นต้องเข้ารหัสอีกครั้งใน UserDetailsService

ในSpring security 5 เอกสารที่พวกเขาแนะนำให้คุณจัดการข้อยกเว้นนี้ได้โดยใช้:

การมอบหมายรหัสผ่านEncoder.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);
    }
}

โปรดแนะนำฉันเกี่ยวกับปัญหานี้ ฉันใช้เวลาหลายชั่วโมงในการแก้ไขปัญหานี้ แต่ไม่สามารถแก้ไขได้


comment
ฉันอธิบายปัญหานี้ได้มากกว่านี้เล็กน้อย เมื่อฉันเปลี่ยนจาก Spring security 4 เป็น 5 ฉันได้รับข้อผิดพลาดแรก จากนั้นฉันก็แก้ไขมันด้วยการเปลี่ยนตัวสร้างรหัสผ่าน มันเริ่มทำให้ฉันมีข้อผิดพลาดครั้งที่สอง และข้อความแสดงข้อผิดพลาดจะแตกต่างกัน 1) รหัสผ่านที่เข้ารหัสดูไม่เหมือน BCrypt และ 2) java.lang.IllegalArgumentException: ไม่มีการแมป PasswordEncoder สำหรับ id null ปัญหาที่สองที่ฉันมีในปัจจุบัน   -  person Jimmy    schedule 05.04.2018
comment
ฉันเชื่อว่าปัญหาเกิดขึ้นกับลูกค้า (ไม่ใช่บัญชีผู้ใช้แต่ละราย) โดยส่วนตัวแล้วฉันได้เข้ารหัสรายละเอียดผู้ใช้แล้ว แต่ไม่ใช่ไคลเอนต์ ขณะนี้ ผู้ใช้ OAuth2 จะต้องเข้ารหัสข้อมูลลับของ ไคลเอ็นต์ (รวมถึงรหัสผ่านของผู้ใช้) โดยเฉพาะ ให้ตั้งค่า passwordEncoder บน ClientDetailsServiceConfigurer หรือนำหน้าข้อมูลลับด้วย {noop} หวังว่าจะสมเหตุสมผลและช่วยเหลือใครสักคน   -  person KellyM    schedule 06.04.2018
comment
ปัญหานี้ได้รับการแก้ไขสำหรับฉันด้วยการเรียกใช้ mvn clean package คงมีปัญหาบางอย่างกับการแคช   -  person Janac Meena    schedule 26.02.2021


คำตอบ (8)


เมื่อคุณกำหนดค่า ClientDetailsServiceConfigurer คุณจะต้องใช้ รูปแบบการจัดเก็บรหัสผ่านไปยังรหัสลับไคลเอ็นต์

.secret("{noop}secret")
person Edwin Diaz    schedule 06.04.2018
comment
ดูเพิ่มเติมที่ การจับคู่รหัสผ่าน และ รูปแบบการจัดเก็บรหัสผ่านใน Spring Security 5.0.0.RC1 ข้อสังเกตที่เผยแพร่ - person olivmir; 09.04.2018
comment
ยังสามารถเพิ่ม secret(passwordEncoder.encode("secret")) ได้โดยใช้ตัวเข้ารหัสรหัสผ่านเริ่มต้น - person Troy Young; 04.01.2019
comment
ถ้าฉันไม่ใช้ความลับล่ะ? - person f.khantsis; 10.02.2019
comment
ด้วย: auth.inMemoryAuthentication() .withUser(admin).roles(ADMIN).password({noop}password); - person bzhu; 01.09.2020

เพิ่ม .password("{noop}password") ลงในไฟล์กำหนดค่าความปลอดภัย

ตัวอย่างเช่น :

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");
person Sailokesh Aithagoni    schedule 07.06.2019
comment
คุณบอกฉันได้ไหมว่าทำไมคุณถึงเพิ่ม {noop}? - person Indrajeet Gour; 12.08.2019
comment
แค่ต้องการใช้คำรหัสผ่านธรรมดา...! เหมือนไม่มีการผ่าตัดใด ๆ เกิดขึ้น! ฉันคิดอย่างนั้น! ;) - person Sailokesh Aithagoni; 16.08.2019

สำหรับใครก็ตามที่ประสบปัญหาเดียวกันและไม่ต้องการโซลูชันที่ปลอดภัย - สำหรับการทดสอบและแก้ไขข้อบกพร่องเป็นหลัก - ผู้ใช้ในหน่วยความจำยังสามารถกำหนดค่าได้

นี่เป็นเพียงการเล่นเท่านั้น ไม่มีสถานการณ์ในโลกแห่งความเป็นจริง

แนวทางที่ใช้ด้านล่างนี้เลิกใช้แล้ว

นี่คือที่ฉันได้รับมาจาก:


ภายใน WebSecurityConfigurerAdapter ของคุณให้เพิ่มสิ่งต่อไปนี้:

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

เห็นได้ชัดว่ารหัสผ่านถูกแฮช แต่ยังคงมีอยู่ในหน่วยความจำ


แน่นอน คุณสามารถใช้ PasswordEncoder จริงเช่น BCryptPasswordEncoder และนำหน้ารหัสผ่านด้วยรหัสที่ถูกต้อง:

// 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
สวัสดี จริงๆ แล้วเราไม่สามารถใช้ NoOpPasswordEncoder ได้ เนื่องจากเลิกใช้แล้วในเวอร์ชัน spring-security ใหม่ และตามเอกสารความปลอดภัยของสปริง (spring.io/blog/2017/11/01/) แม้ว่าเราจะใช้ NoOpPasswordEncoder เราจะต้องเพิ่ม {noop} เป็นรหัสให้กับรหัสผ่านและความลับ ฉันเชื่อว่านี่จะไม่พิสูจน์คำถามของฉัน ปัญหาหลักของวิธีแก้ปัญหาทั้งหมดคือพวกเขาไม่ได้บอกว่าคุณต้องเพิ่ม ID ให้กับความลับของคุณด้วย - person Jimmy; 15.04.2018
comment
ใช่ มันเลิกใช้แล้ว แหล่งที่มาของฉันก็บอกเช่นกัน หากคุณเพียงแค่ใช้ NoOpPasswordEncoder - ไม่มี BCryptPasswordEncoder - มันก็ใช้ได้ ฉันใช้สปริงบูท 2.0.1.RELEASE - person rocksteady; 15.04.2018
comment
แต่อย่างที่ฉันบอกไปแล้วในคำตอบ นี่ไม่ใช่สถานการณ์การผลิตเลย - person rocksteady; 15.04.2018
comment
ฉันไม่เข้าใจว่าทำไมเราจึงควรใช้คลาสที่เลิกใช้แล้วเพื่อการทดสอบ - person Jimmy; 16.04.2018
comment
ตกลง คุณพูดถูกแล้ว ฉันเพิ่มคำตอบนี้สำหรับทุกคนที่เพิ่งเล่นและเริ่มดำน้ำในเรื่องนี้โดยใช้บทช่วยสอนที่ล้าสมัย นั่นเป็นเหตุผลที่ฉันพูดถึง BCryptPasswordEncoder ด้วย ฉันอัปเดตคำตอบของฉันแล้ว - person rocksteady; 16.04.2018
comment
คำตอบช่วยฉันในสถานการณ์การทดสอบขอบคุณ - person Osama Abdulsattar; 15.01.2019
comment
นั่นเป็นเหตุผลที่ฉันพูดอย่างนั้นในตอนต้นของคำตอบ - person rocksteady; 04.02.2019

ไม่รู้ว่านี่จะช่วยใครได้บ้าง รหัส 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

person CrownWangGuan    schedule 21.01.2020

เมื่อใดก็ตามที่ 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 ให้กับผู้ให้บริการตรวจสอบสิทธิ์ของคุณด้วย ฉันทำอย่างนั้นด้วยบรรทัดด้านล่างในคลาส config ของฉัน

@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

หากคุณกำลังดึงชื่อผู้ใช้และรหัสผ่านจากฐานข้อมูล คุณสามารถใช้โค้ดด้านล่างเพื่อเพิ่มอินสแตนซ์ NoOpPassword

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

โดยที่ adm เป็นวัตถุผู้ใช้ที่กำหนดเองสำหรับโครงการของฉันซึ่งมีเมธอด getPassword() และ getUsername()

นอกจากนี้ โปรดจำไว้ว่า หากต้องการสร้าง User POJO แบบกำหนดเอง คุณจะต้องใช้อินเทอร์เฟซ UserDetails และใช้วิธีการทั้งหมด

หวังว่านี่จะช่วยได้

person Ashish Singh    schedule 16.05.2020

คุณสามารถอ่าน ได้ใน เอกสาร Spring Security อย่างเป็นทางการ ซึ่งสำหรับ DelegatingPasswordEncoder รูปแบบทั่วไปของรหัสผ่านคือ: {id}encodedPassword

รหัสดังกล่าวเป็นตัวระบุที่ใช้ค้นหาว่าควรใช้ PasswordEncoder ใด และ encodedPassword เป็นรหัสผ่านที่เข้ารหัสดั้งเดิมสำหรับ PasswordEncoder ที่เลือก รหัสจะต้องอยู่ที่จุดเริ่มต้นของรหัสผ่าน โดยขึ้นต้นด้วย { และลงท้ายด้วย } หากไม่พบ ID ID จะเป็นโมฆะ ตัวอย่างเช่น รายการต่อไปนี้อาจเป็นรายการรหัสผ่านที่เข้ารหัสโดยใช้ ID อื่น รหัสผ่านเดิมทั้งหมดคือ "รหัสผ่าน"

ตัวอย่างรหัสคือ:

{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG {noop}รหัสผ่าน {pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde724 45e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc {การเข้ารหัส}$e0801 $8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0

person Continuity8    schedule 07.05.2020
comment
ปัญหานั้นเป็นเพราะเราจำเป็นต้องมีประเภทการเข้ารหัสในความลับของลูกค้าด้วย ณ ขณะนี้รหัสผ่านได้เพิ่มประเภทการเข้ารหัสต่อท้ายรหัสผ่านที่เข้ารหัสแล้ว - person Jimmy; 08.05.2020

เกี่ยวกับ

รหัสผ่านที่เข้ารหัสดูไม่เหมือน BCrypt

ในกรณีของฉัน มีความไม่ตรงกันในความแข็งแกร่งของ BCryptPasswordEncoder ที่ใช้โดยตัวสร้างเริ่มต้น (10) เนื่องจาก pwd hash ถูกสร้างขึ้นด้วยความแข็งแกร่ง 4 ดังนั้นฉันจึงได้กำหนดความแข็งแกร่งไว้อย่างชัดเจน

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

Spring Security ของฉันคือเวอร์ชัน 5.1.6 และทำงานได้อย่างสมบูรณ์แบบกับ BCryptPasswordEncoder

person Bender    schedule 13.08.2019