Переход на Spring Boot 2 - пароль, закодированный безопасностью, не похож на BCrypt

У меня был сервер авторизации Spring Boot 1.5.9, который использует BCrypt для хранения паролей. Я пытаюсь перейти на 2.0. Однако я больше не могу получить токен для авторизации.

Ответ сервера:

    "timestamp": "2018-03-09T15:22:06.576+0000",
    "status": 401,
    "error": "Unauthorized",
    "message": "Unauthorized",
    "path": "/oauth/token"
}

при этом консоль выводит следующее: 2018-03-09 09:22:06.553 WARN 20976 --- [nio-8090-exec-1] o.s.s.c.bcrypt.BCryptPasswordEncoder : Encoded password does not look like BCrypt.

Раньше эта часть приложения работала нормально. Единственные изменения, которые я внес, были в файл build.gradle (изменение springBootVersion, добавление плагина io.spring.dependency-management и добавление runtime('org.springframework.boot:spring-boot-devtools').

buildscript {
    ext {
        springBootVersion = '2.0.0.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.midamcorp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8

repositories {
    mavenCentral()
}



dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-rest')
    compile('org.springframework.boot:spring-boot-starter-jdbc')
        compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('commons-io:commons-io:2.5')    
    compile('org.springframework.security:spring-security-jwt:1.0.7.RELEASE')
    compile('org.springframework.security.oauth:spring-security-oauth2:2.2.1.RELEASE')
    compile 'com.microsoft.sqlserver:mssql-jdbc:6.2.2.jre8'   
    runtime('org.springframework.boot:spring-boot-devtools')

    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.security:spring-security-test')
testCompile('com.h2database:h2:1.4.196')

}

Логика хеширования паролей находится в двух отдельных файлах конфигурации:

package com.midamcorp.auth_server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.midamcorp.auth_server.service.OAuthUserDetailsService;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private OAuthUserDetailsService userService;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
}

    // Hash password
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService)
        .passwordEncoder(passwordEncoder);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
       http
               .sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
               .and()
               .httpBasic()
               .realmName("test")
               .and()
               .csrf()
               .disable();

    }
}

а также

package com.midamcorp.auth_server.config;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;


// Contains properties common to both authorization and resource servers
@Configuration
public class AppConfig {


        @Value("${spring.datasource.url}")
        private String datasourceUrl;

        @Value("${spring.datasource.driverClassName}")
        private String dbDriverClassName;

        @Value("${spring.datasource.username}")
        private String dbUsername;

        @Value("${spring.datasource.password}")
        private String dbPassword;

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

        @Bean
        public DataSource dataSource() {
            final DriverManagerDataSource dataSource = new DriverManagerDataSource();

            dataSource.setDriverClassName(dbDriverClassName);
            dataSource.setUrl(datasourceUrl);
            dataSource.setUsername(dbUsername);
            dataSource.setPassword(dbPassword);

            return dataSource;
        }    

        // Refrence: http://www.baeldung.com/spring-security-oauth-jwt

        /* !!!!!!!!!!!!!!!!!!!!!!!!!! 
        ** TODO 
        * Secure key file for deployment.
        !!!!!!!!!!!!!!!!!!!! */
           @Bean
           public JwtAccessTokenConverter accessTokenConverter() {
              JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
              KeyStoreKeyFactory keyStoreKeyFactory = 
                      new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"), "mypass".toCharArray());
                    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
              return converter;
           }


           @Bean
           public TokenStore tokenStore() {
              return new JwtTokenStore(accessTokenConverter());
           }


}

OAuthUser класс:

    package com.midamcorp.auth_server.model;


import java.util.List;

import javax.persistence.*;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;

import com.fasterxml.jackson.annotation.JsonIgnore;


@Entity
@Table(name = "auth_user")
public class OAuthUser {

//    @Autowired 
//    @Transient
//    private PasswordEncoder passwordEncoder;
//    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name= "username")
    private String userName;

    @Column(name = "password")
    @JsonIgnore
    private String password;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "email")
    private String email;

    @Column(name = "is_enabled")
    private boolean isEnabled;

     /**
      * Reference: https://github.com/nydiarra/springboot-jwt/blob/master/src/main/java/com/nouhoun/springboot/jwt/integration/domain/User.java
     * Roles are being eagerly loaded here because
     * they are a fairly small collection of items for this example.
     */
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns
            = @JoinColumn(name = "user_id",
            referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "role_id",
                    referencedColumnName = "id"))
private List<Role> roles;


    public OAuthUser() {};
    public OAuthUser(String firstName, String lastName, String user, String pass) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.userName = user;
        this.password = pass;
    }
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public boolean isEnabled() {
        return isEnabled;
    }

    public void setEnabled(boolean isEnabled) {
        this.isEnabled = isEnabled;
    }
}

Я понимаю, что в Spring Security были внесены значительные изменения, но я не уверен, что подойду к решению этой проблемы. Любое руководство будет оценено.

Спасибо.

РЕДАКТИРОВАТЬ

Еще несколько подробностей на случай, если они помогут. Даже если я добавлю нового пользователя при запуске Spring Boot 2.0:

OAuthUser user = new OAuthUser();   

            user.setFirstName("K");
            user.setLastName("M");
            user.setPassword(passwordEncoder.encode("L"));
            user.setUserName("KLM");

repository.save(user);

это и сделать запрос, используя новое имя пользователя и пароль, я все равно получаю сообщение об ошибке.

ИЗМЕНИТЬ ДВА:

Запросить результаты по ошибке:

curl --request POST \
  --url http://web:secret@localhost:8090/oauth/token \
  --header 'content-type: multipart/form-data; boundary=---011000010111000001101001' \
  --form grant_type=password \
  --form username=KLM \
  --form password=L

Конфигурация сервера авторизации:

    package com.midamcorp.auth_server.config;

import java.util.Arrays;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationManager;
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.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AccessTokenConverter;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

// Reference: https://dazito.com/java/spring-boot-and-oauth2-with-jdbc

@EnableAuthorizationServer
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter{
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private AccessTokenConverter converter;

     private final AppConfig appConfig; 

    private AuthenticationManager authenticationManager;

    @Autowired
    public AuthServerConfig(AuthenticationManager authenticationManager, AppConfig appConfig) {
        this.authenticationManager = authenticationManager;
        this.appConfig = appConfig;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()");
        security.tokenKeyAccess("permitAll()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

    JdbcClientDetailsService details = new JdbcClientDetailsService(appConfig.dataSource());

        configurer.jdbc(appConfig.dataSource());
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        endpoints.tokenStore(tokenStore)
                .accessTokenConverter(converter)
            .authenticationManager(authenticationManager);
    }



       @Bean
       @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
       public DefaultTokenServices tokenServices() {
          DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
          defaultTokenServices.setTokenStore(tokenStore);
          defaultTokenServices.setSupportRefreshToken(true);
          return defaultTokenServices;
       }       



}

Я использую следующие свойства:

 spring.datasource.url=jdbc:sqlserver://localhost;databaseName=API
spring.datasource.username=**
spring.datasource.password=**
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
server.port=8090

@dur Конечно, значение "L" (пароль для нового пользователя, указанный выше): $ 2a $ 10 $ YI5SRl5OwiLS // AL.zGo / O6YxLWogcbIPkV54Zp9F7TXZ4Tk6JXRe

KellyM 09.03.2018 19:18

@dur Это пароль пользователя. Я добавил тот же запрос curl (сгенерированный с помощью клиента Insomnia) выше. Спасибо.

KellyM 09.03.2018 19:26

Spring-boot 2 фактически включал переименование многих свойств. Вы просмотрели примечания к выпуску, чтобы убедиться, что вы не используете свойство, которое было переименовано, но не обновлено?

Roddy of the Frozen Peas 09.03.2018 19:33

@dur, я попытался ввести кодировщик паролей @Autowire и передать его следующим образом: @Override public void configure(ClientDetailsServiceConfigurer configurer) throws Exception { JdbcClientDetailsService details = new JdbcClientDetailsService(appConfig.dataSource()); details.setPasswordEncoder(passwordEncoder); configurer.withClientDetails(details); // configurer.jdbc(appConfig.dataSource()); }, но безуспешно. @Roddy of the Frozen Peas, спасибо, проверю. Я добавил свойства, которые использую для справки.

KellyM 09.03.2018 19:39

Вы можете обратиться к руководство по миграции, который конкретно вызывает безопасность и проект OAuth 2.

Roddy of the Frozen Peas 09.03.2018 19:43

Вы используете Spring-Security 5? Если это так, возможно, вам придется перенести свои пароли и добавить к ним соответствующую нотацию кодировщика, как описано здесь spring.io/blog/2017/11.01/…

dmendezg 09.03.2018 23:51

Вы решили проблему? У меня такая же проблема с новым приложением с Spring Boot 2. Я использую новый формат пароля, тестирование декодирования работает, но я получаю ту же ошибку при попытке получить токен.

alex 14.03.2018 16:00

У меня был такой же журнал «Закодированный пароль не похож на BCrypt». Это произошло потому, что мой пароль в базе данных не был зашифрован с помощью BCrypt, поэтому я сделал что-то вроде этого: passwordEncoder (). encode ("mypassword"); и сохраните результат в базе данных, теперь он работает.

vicmac 12.05.2018 04:43
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
10
8
7 873
3

Ответы 3

Я решил кодировать пароль clientSecret

@Override
public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
    configurer
            .inMemory()
            .withClient(clientId)
            .secret(encode(clientSecret))
            .authorizedGrantTypes(grantType)
            .scopes(scopeRead, scopeWrite)
            .resourceIds(resourceIds);
}

Когда зависимости oauth2 переместились в облако, я столкнулся с этой проблемой. Раньше это было частью системы безопасности:

<dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId></dependency>

Теперь это часть облачного фреймворка:

<dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>

Поэтому, если вы используете облачную зависимость (Finchley.RELEASE), вам может потребоваться кодировать секрет, как показано ниже:

@Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
         clients
                .inMemory()
                .withClient("clientapp")
                .authorizedGrantTypes("password","refresh_token")
                .authorities("USER")
                .scopes("read", "write")
                .resourceIds(RESOURCE_ID)
                .secret(passwordEncoder.encode("SECRET"));
    }

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

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

passwordEncoder.encode(rawPassword, hashedPassword)

Порядок ввода параметра важен. Первым параметром должен быть rawPassword, а вторым - hashedPassword. У меня возникла эта проблема, потому что я поменял местами последовательность входных параметров.

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