Spring Security 5: нет PasswordEncoder, сопоставленного с нулевым идентификатором

Я перехожу с Spring Boot 1.4.9 на Spring Boot 2.0, а также на Spring Security 5, и я пытаюсь выполнить аутентификацию через OAuth 2. Но я получаю эту ошибку:

java.lang.IllegalArgumentException: не существует PasswordEncoder, сопоставленного с идентификатором "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, сопоставленного с идентификатором "null"

Чтобы решить эту проблему, я попробовал все следующие вопросы из Stackoverflow:

Вот вопрос, похожий на мой, но без ответа:

ПРИМЕЧАНИЕ. Я уже храню зашифрованный пароль в базе данных, поэтому нет необходимости повторно кодировать в UserDetailsService.

В Spring security 5 они предложили обработать это исключение, используя:

DelegatingPasswordEncoder.setDefaultPasswordEncoderForMatches (PasswordEncoder)

Если это исправление, то где мне его поставить? Я пытался поместить его в компонент PasswordEncoder, как показано ниже, но он не работал:

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

Пожалуйста, помогите мне решить эту проблему. У меня есть часы, чтобы исправить это, но я не могу исправить.


person Jimmy    schedule 04.04.2018    source источник
comment
Я немного описал проблему. Когда я перешел с Spring security 4 на 5. Я получал первую ошибку, а затем решил ее, изменив свой генератор паролей. Он начал выдавать мне вторую ошибку. И сообщения об ошибках разные. 1) Закодированный пароль не похож на BCrypt и 2) java.lang.IllegalArgumentException: нет PasswordEncoder, сопоставленного с нулевым идентификатором. Вторые проблемы, которые у меня возникают в настоящее время.   -  person Jimmy    schedule 05.04.2018
comment
Я считаю, что проблема связана с клиентом (а не с отдельными учетными записями пользователей). Я лично уже кодировал данные пользователя, но не клиента. Теперь ожидается, что пользователи OAuth2 будут кодировать секрет client (а также пароли пользователей). В частности, либо установите 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} пароль); - 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 (spring.io/blog/2017/11/01/), даже если мы используем NoOpPasswordEncoder, мы должны добавить {noop} в качестве идентификатора к паролю и секрету. Я считаю, что это не оправдывает мой вопрос. Основная проблема со всеми решениями заключается в том, что они не упоминают, что вам также нужно добавить идентификатор к вашему секрету. - 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);
    }
}

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

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 вашему провайдеру аутентификации. Я сделал это с помощью следующих строк в моих классах конфигурации.

@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 ().

Также помните, что для создания пользовательского POJO пользователя вам необходимо реализовать интерфейс UserDetails и реализовать все его методы.

Надеюсь это поможет.

person Ashish Singh    schedule 16.05.2020

Вы можете прочитать в официальная документация по безопасности Spring, в которой DelegatingPasswordEncoder общий формат пароля: {id} encodedPassword

Таким образом, id - это идентификатор, используемый для поиска того, какой PasswordEncoder следует использовать, а encodedPassword - это исходный закодированный пароль для выбранного PasswordEncoder. Идентификатор должен стоять в начале пароля, начинаться с {и заканчиваться}. Если идентификатор не может быть найден, он будет нулевым. Например, ниже может быть список паролей, закодированных с использованием другого идентификатора. Все исходные пароли - это «пароли».

Примеры идентификаторов:

{bcrypt} $ 2a $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG {noop} пароль {pbkdf2} 5d923b44a6d129f3ddf4e7b63dfc08c8d3d3d3d3d05d5d8d5d8d07d8d5d5d5ddddd8d8dd5dd8d8f6dddd8dddd5dddddd5 $ 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 был сгенерирован с силой 4. Поэтому я установил силу явным образом.

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

также моя версия Spring Security - 5.1.6, и она отлично работает с BCryptPasswordEncoder

person Bender    schedule 13.08.2019