У меня был сервер авторизации 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 Это пароль пользователя. Я добавил тот же запрос curl (сгенерированный с помощью клиента Insomnia) выше. Спасибо.
Spring-boot 2 фактически включал переименование многих свойств. Вы просмотрели примечания к выпуску, чтобы убедиться, что вы не используете свойство, которое было переименовано, но не обновлено?
@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, спасибо, проверю. Я добавил свойства, которые использую для справки.
Вы можете обратиться к руководство по миграции, который конкретно вызывает безопасность и проект OAuth 2.
Вы используете Spring-Security 5? Если это так, возможно, вам придется перенести свои пароли и добавить к ним соответствующую нотацию кодировщика, как описано здесь spring.io/blog/2017/11.01/…
Вы решили проблему? У меня такая же проблема с новым приложением с Spring Boot 2. Я использую новый формат пароля, тестирование декодирования работает, но я получаю ту же ошибку при попытке получить токен.
У меня был такой же журнал «Закодированный пароль не похож на BCrypt». Это произошло потому, что мой пароль в базе данных не был зашифрован с помощью BCrypt, поэтому я сделал что-то вроде этого: passwordEncoder (). encode ("mypassword"); и сохраните результат в базе данных, теперь он работает.




Я решил кодировать пароль 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. У меня возникла эта проблема, потому что я поменял местами последовательность входных параметров.
@dur Конечно, значение "L" (пароль для нового пользователя, указанный выше): $ 2a $ 10 $ YI5SRl5OwiLS // AL.zGo / O6YxLWogcbIPkV54Zp9F7TXZ4Tk6JXRe