我正在从Spring Boot 1.4.9迁移到Spring Boot 2.0以及Spring Security 5,我正在尝试通过OAuth 2进行身份验证。但是我收到了这个错误:
java.lang.IllegalArgumentException:没有为id" null
映射PasswordEncoder
从Spring Security 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:没有为id" null
映射PasswordEncoder
为了解决这个问题,我尝试了Stackoverflow的所有以下问题:
这是一个类似于我的问题,但没有回答:
注意:我已将加密密码存储在数据库中,因此无需再次在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);
}
}
请指导我这个问题。我花了好几个小时来解决这个问题,但无法修复。
答案 0 :(得分:47)
配置ClientDetailsServiceConfigurer
时,您还必须将新的password storage format应用于客户端密钥。
.secret("{noop}secret")
答案 1 :(得分:7)
对于任何面临同样问题并且不需要安全解决方案的人 - 主要用于测试和调试 - 在内存中仍然可以配置用户。
这只是为了玩游戏 - 没有现实世界的场景。
以下使用的方法已弃用。
这是我从以下地方获得的:
在WebSecurityConfigurerAdapter
内添加以下内容:
@SuppressWarnings("deprecation")
@Bean
public static NoOpPasswordEncoder passwordEncoder() {
return (NoOpPasswordEncoder) NoOpPasswordEncoder.getInstance();
}
显然,密码是经过哈希处理的,但仍可在内存中使用。
当然,您也可以使用PasswordEncoder
之类的真实BCryptPasswordEncoder
,并在密码前加上正确的ID:
// Create an encoder with strength 16
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(16);
String result = encoder.encode("myPassword");
assertTrue(encoder.matches("myPassword", result));
答案 2 :(得分:2)
将.password("{noop}password")
添加到“安全性”配置文件。
例如:
auth.inMemoryAuthentication()
.withUser("admin").roles("ADMIN").password("{noop}password");
答案 3 :(得分:2)
不知道这是否会帮助任何人。我正在工作的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);
}
}
答案 4 :(得分:1)
每当Spring存储密码时,它都会在编码的密码(例如bcrypt,scrypt,pbkdf2等)中放置编码器前缀,以便在需要解码密码时可以使用适当的编码器进行解码。如果编码的密码中没有前缀,则使用defaultPasswordEncoderForMatches。您可以查看DelegatingPasswordEncoder.class的matchs方法,以了解其工作原理。因此,基本上,我们需要通过以下几行设置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 :(得分:0)
对于DelegatingPasswordEncoder
,您可以阅读in the official Spring Security Documentation,密码的一般格式为: {id} encodedPassword
该id是用于查找应使用哪个PasswordEncoder的标识符,而encodePassword是所选PasswordEncoder的原始编码密码。 ID必须在密码的开头,以{开头,以}结尾。 如果找不到该ID,则该ID将为空。例如,以下可能是使用不同ID编码的密码列表。所有原始密码均为“密码”。
Id示例为:
{ bcrypt } $ 2a $ 10 $ dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM / BG {noop}密码 { pbkdf2 } 5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc { scrypt } $ e0801 $ 8bWJaSu2IKSn9Z9kM + TPXfOc / 9bdYSrN1oD9qfVThWEwdRTnO7re7Ei + fUZRJ68k9lTyuTeUp4of4g24hHnzu == $ OAOEC05 + BXXYY1Y6YQ6 { sha256 } 97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
答案 6 :(得分:0)
如果您要从数据库中获取用户名和密码, 您可以使用以下代码添加NoOpPassword实例。
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(adm).passwordEncoder(NoOpPasswordEncoder.getInstance());
}
adm 是我的项目的自定义用户对象,具有getPassword()和getUsername()方法。
还请记住,要创建自定义用户POJO,您必须实现UserDetails接口并实现其所有方法。
希望这会有所帮助。
答案 7 :(得分:-1)
关于
编码密码看起来不像BCrypt
在我的情况下,默认构造函数(10)使用的BCryptPasswordEncoder强度不匹配,因为pwd哈希是使用强度4生成的。因此,我将强度设置为显式。
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(4);
}
我的Spring Security版本也是5.1.6,它与BCryptPasswordEncoder完美配合