[英]User authentication in oauth2 with Spring Boot
I have implemented OAuth 2.0 with spring boot. 我已经用Spring Boot实现了OAuth 2.0。 I am storing the hashed password (including a salt) into my database.
我将哈希密码(包括盐)存储到数据库中。
I'm new to Spring and can't figure out how could I authenticate a username/password. 我是Spring的新手,无法弄清楚如何验证用户名/密码。
This is my UserDAO class: 这是我的UserDAO类:
@Service
public class UserDAO implements UserDetailsService{
@Autowired
private LoginDetailsManager loginDetailsManager;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("Get user");
LoginDetails user = loginDetailsManager.getByUsername(username);
System.out.println(user.toString());
if (user == null) {
// Not found...
throw new UsernameNotFoundException(
"User " + username + " not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(grantedAuthority);
String password = user.getPasswordHash();
String salt = user.getSalt();
return new UserDetailsImpl(
user.getUsername(),
user.getPasswordHash(),
salt,
grantedAuthorities);
}
}
And this is the security configurer from the Application class: 这是来自Application类的安全配置器:
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients();
}
Questions: 问题:
Retrieving the principal, providing a salt and encoding respectively matching the password are all decoupled from each other in Spring Security. 在Spring Security中,检索主体,提供分别与密码匹配的salt和编码都相互分离。 Your UserDetails service is basically a correct way of fetching the principal.
您的UserDetails服务基本上是获取主体的正确方法。
You have to provide a PasswordEncoder, the default in Spring Boot is BCrypt, which doesn't match your hash. 您必须提供一个PasswordEncoder,Spring Boot中的默认值为BCrypt,它与您的哈希不匹配。 The old password encoder (ShaPasswordEncoder and such) as well as the salt sources are no longer picked up by Spring Boot and Spring Security.
Spring Boot和Spring Security不再使用旧的密码编码器(ShaPasswordEncoder等)以及盐源。 You have to define them manually.
您必须手动定义它们。
This configuration should basically do what you want, given you have a UserDetails
that contains a salt column. 假设您有一个包含Salt列的
UserDetails
,则此配置基本上应该可以完成您想要的操作。 If you don't know how to do that, here's the complete implementation ShaPasswordEncoderConfig.java from my upcoming book. 如果您不知道该怎么做,这是我即将出版的书中的完整实现ShaPasswordEncoderConfig.java 。
@Configuration
public class ShaPasswordEncoderConfig
extends WebSecurityConfigurerAdapter {
final UserDetailsService userDetailsService;
public ShaPasswordEncoderConfig(final UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
public void configure(
AuthenticationManagerBuilder auth
) {
DaoAuthenticationProvider authProvider
= new DaoAuthenticationProvider();
authProvider.setUserDetailsService(
userDetailsService);
authProvider.setPasswordEncoder(
new ShaPasswordEncoder(256));
authProvider.setSaltSource(
user -> ((UserWithSalt)user).getSalt());
auth.authenticationProvider(authProvider);
}
}
To make this work, you have to role your own UserDetails implementation and not use that one from Spring Security itself, since it has no attribute to store the salt. 为了使这项工作有效,您必须扮演自己的UserDetails实现角色,而不能使用Spring Security本身的实现,因为它没有存储盐的属性。
The SaltSource
I used in the example is a lambda that that casts a UserDetails
to my implementation and grabs the salt. 我在示例中使用的
SaltSource
是一个lambda,它将一个UserDetails
强制转换为我的实现并捕获了盐。 You can also use ReflectionSaltSource
which is one implementation that gets the salt via reflection. 您还可以使用
ReflectionSaltSource
,这是一种通过反射获取盐分的实现。
Edit 编辑
UserDetails
is easy to implement, see: UserDetails
易于实现,请参见:
You could use an implementation like this: 您可以使用以下实现:
class UserDetailsImpl implements UserDetails {
private final String username;
private final String hashedPassword;
private final String salt;
private final List<GrantedAuthority> grantedAuthorities;
public UserDetailsImpl(String username, String hashedPassword, String salt, List<GrantedAuthority> grantedAuthorities) {
this.username = username;
this.hashedPassword = hashedPassword;
this.salt = salt;
this.grantedAuthorities = grantedAuthorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return grantedAuthorities;
}
@Override
public String getPassword() {
return hashedPassword;
}
@Override
public String getUsername() {
return username;
}
public String getSalt() {
return salt;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
I ended up implementing my own PasswordEncoder
interface and using a local static variable to pass it the salt through db. 我最终实现了自己的
PasswordEncoder
接口,并使用一个本地静态变量将它通过db传递给了salt。
The implementation is experimental, based on my observation of flow of control: 根据我对控制流的观察,该实现是实验性的:
loadUserByUsername
in userDAO. loadUserByUsername
。 public class PasswordEncoderImpl implements PasswordEncoder {
public static Constants constants = new Constants();
@Override
public String encode(CharSequence rawPassword) {
return DigestUtils.sha256Hex(rawPassword.toString());
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String salt = constants.getSalt();
/**
* If salt is null, it means the value in constants object is empty now.
* => client-secret validation is going on
* => we just need the comparision of plain-text string.
*/
if(salt != null) {
// case of password authentication
return DigestUtils.sha256Hex(salt + rawPassword).equalsIgnoreCase(encodedPassword);
} else {
//case of client-secret authentication
return rawPassword.equals(encodedPassword);
}
}
}
In my UserDAO, where I get the user-details from the persistence, I would store salt into the object constants before return the user: 在我的UserDAO中,从持久性中获取用户详细信息,在返回用户之前,我会将salt存储到对象常量中:
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("Get user");
user = loginDetailsManager.getByUsername(username);
System.out.println(user.toString());
if (user == null) {
// Not found...
throw new UsernameNotFoundException(
"User " + username + " not found.");
}
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(grantedAuthority);
constants.set(user.getSalt());
return new User(
user.getUsername(),
user.getPasswordHash(),
true, true, true, true,
grantedAuthorities);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.