Spring Безопасность 5: отсутствует идентификатор PasswordEncoder для идентификатора "null"

Я перехожу из 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:

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

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

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

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

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

Ответ 1

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

.secret("{noop}secret")

Ответ 2

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

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

Подход, используемый ниже, не рекомендуется.

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


В вашем WebSecurityConfigurerAdapter добавьте следующее:

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

Здесь, очевидно, пароли хешируются, но все же доступны в памяти.


Конечно, вы также можете использовать настоящий PasswordEncoder такой как BCryptPasswordEncoder и BCryptPasswordEncoder перед паролем правильный идентификатор:

// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));

Ответ 3

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

Например:

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

Ответ 4

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

Ответ 5

Относительно

Кодированный пароль не похож на BCrypt

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

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

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

Ответ 6

Эта ошибка может произойти, если вы по ошибке забыли использовать аннотацию "@Bean" в своей бизнес-логике.

  @Bean 
  public AuthenticationProvider authProvider() {
      DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
      provider.setUserDetailsService(userDetailsService);
      provider.setPasswordEncoder(NoOpPasswordEncoder.getInstance()); 
      return provider; 
  }