Spring Security 5: PasswordEncoder не сопоставлен с идентификатором "null"

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

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

Из документации Весенняя безопасность 5 я узнал, что изменен формат хранения пароля.

В моем текущем коде я создал bean-компонент кодировщика паролей как:

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

Однако это давало мне ошибку ниже:

Encoded password does not look like BCrypt

Поэтому я обновляю кодировщик в соответствии с документом Весенняя безопасность 5, чтобы:

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

Теперь, если я вижу пароль в базе данных, он хранится как

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

Эта первая ошибка исчезла, и теперь, когда я пытаюсь выполнить аутентификацию, я получаю ошибку ниже:

java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null

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

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

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

В документации Весенняя безопасность 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);
    }
}

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

Я немного описал проблему. Когда я перешел с Spring security 4 на 5. Я получал первую ошибку, а затем решил ее, изменив свой генератор паролей. Он начал выдавать мне вторую ошибку. И сообщения об ошибках разные. 1) Закодированный пароль не похож на BCrypt и 2) java.lang.IllegalArgumentException: нет PasswordEncoder, сопоставленного с идентификатором "null. Вторая проблема, которая у меня есть в настоящее время.

Jimmy 05.04.2018 14:20

Я считаю, что проблема связана с клиентом (а не с отдельными учетными записями пользователей). Я лично уже кодировал данные о пользователе, но не о клиенте. Теперь ожидается, что пользователи OAuth2 будут кодировать секрет клиент (а также пароли пользователей). В частности, либо установите passwordEncoder на ClientDetailsServiceConfigurer, либо префикс секрета с помощью {noop}. Надеюсь, что это имеет смысл и кому-то поможет.

KellyM 06.04.2018 02:46

Эта проблема была решена для меня, запустив mvn clean package. Должно быть какая-то проблема с кешированием.

Janac Meena 26.02.2021 20:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
74
3
90 356
8
Перейти к ответу Данный вопрос помечен как решенный

Ответы 8

Ответ принят как подходящий

Когда вы настраиваете ClientDetailsServiceConfigurer, вы также должны применить новый формат хранения паролей к секрету клиента.

.secret("{noop}secret")

см. также Соответствие пароля и Формат хранения паролей в Spring Security 5.0.0.RC1 Выпущенные примечания

olivmir 09.04.2018 14:01

также можно добавить secret(passwordEncoder.encode("secret")), используя кодировщик пароля по умолчанию.

Troy Young 04.01.2019 09:41

что, если я не использую секрет?

f.khantsis 10.02.2019 01:08

также: auth.inMemoryAuthentication () .withUser ("admin"). roles ("ADMIN"). password ("{noop} password") ‌;

bzhu 01.09.2020 08:44

Для всех, кто сталкивается с той же проблемой и не нуждается в безопасном решении - в основном для тестирования и отладки - пользователи по-прежнему могут быть настроены в памяти.

Это просто для игры - никакого сценария из реального мира.

Используемый ниже подход устарел.

Вот откуда я это взял:


В ваш 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));

Привет, на самом деле мы не можем использовать NoOpPasswordEncoder. Поскольку это устарело в новой версии Spring-Security. И согласно документации по безопасности Spring (spring.io/blog/2017/11/01/…), даже если мы используем NoOpPasswordEncoder, мы должны добавить {noop} в качестве идентификатора к паролю и секрету. Я считаю, что это не оправдывает мой вопрос. Основная проблема со всеми решениями заключается в том, что они не упоминают, что вам также нужно добавить идентификатор к вашему секрету.

Jimmy 15.04.2018 16:05

Да, это не рекомендуется. Так же и мой источник. Если просто использовать NoOpPasswordEncoder - без BCryptPasswordEncoder - работает. Я использую Spring boot 2.0.1.RELEASE.

rocksteady 15.04.2018 16:35

Но, как я уже упоминал в ответе, это вообще не производственный сценарий.

rocksteady 15.04.2018 16:36

Я не понимаю, почему мы должны использовать устаревший класс даже для целей тестирования?

Jimmy 16.04.2018 08:53

Хорошо, вы совершенно правы. Я добавил этот ответ для всех, кто просто играет и начинает погружаться в этот вопрос, возможно, используя устаревшее руководство. Вот почему я также упомянул BCryptPasswordEncoder. Я обновил свой ответ.

rocksteady 16.04.2018 09:03

Ответ помог мне со сценарием тестирования, спасибо

Osama Abdulsattar 15.01.2019 16:25

Вот почему я так сказал в начале ответа.

rocksteady 04.02.2019 19:58

Добавьте .password("{noop}password") в файл конфигурации безопасности.

Например :

auth.inMemoryAuthentication()
        .withUser("admin").roles("ADMIN").password("{noop}password");

скажите, почему вы добавили {noop}?

Indrajeet Gour 12.08.2019 15:52

Просто хочу использовать простой пароль ...! Как будто с этим не нужно проводить никаких операций! Я думаю так! ;)

Sailokesh Aithagoni 16.08.2019 12:09

Касательно

Encoded password does not look like BCrypt

В моем случае было несоответствие в силе BCryptPasswordEncoder, используемой конструктором по умолчанию (10), поскольку хэш pwd был сгенерирован с силой 4. Итак, я установил силу явным образом.

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

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

Всякий раз, когда 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;
    }

Не знаю, поможет ли это кому-нибудь. Мой рабочий код 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-авторизация-сервер-oauth2

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

Such that id is an identifier used to look up which PasswordEncoder should be used and encodedPassword is the original encoded password for the selected PasswordEncoder. The id must be at the beginning of the password, start with { and end with }. If the id cannot be found, the id will be null. For example, the following might be a list of passwords encoded using different id. All of the original passwords are "password".

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

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

Эта проблема связана с тем, что нам также нужен тип шифрования в секрете клиента. На данный момент к зашифрованному паролю уже добавлен тип шифрования.

Jimmy 08.05.2020 05:45

Если вы извлекаете имя пользователя и пароль из базы данных, вы можете использовать приведенный ниже код, чтобы добавить экземпляр NoOpPassword.

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

Где адм - это настраиваемый пользовательский объект для моего проекта, в котором есть методы getPassword () и getUsername ().

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

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

Другие вопросы по теме