在 Spring Security 中,UserDetails 将密码存储为字符串,但我将其存储为 byte[]
Posted
技术标签:
【中文标题】在 Spring Security 中,UserDetails 将密码存储为字符串,但我将其存储为 byte[]【英文标题】:In Spring Security, UserDetails stores password as String but I store it as byte[] 【发布时间】:2021-02-11 04:11:00 【问题描述】:我目前在 UserDetailsServiceImpl java 类中编写 loadUserByUsername 方法,这在许多 springboot 教程中都可以找到。问题出在最后一行
return new org.springframework.security.core.userdetails.User(user.getEmail(), user.getPassword(),
grantedAuthorities);
根据 spring security 文档,user.getPassword() 应该返回一个字符串,但是我使用的是 bcrypt 和 mysql,所以当我存储密码时,我将它作为 binary(60) 存储在 Mysql 中,当我阅读它时从数据库到用户类,它被读入一个
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class users
@Id
private String email;
private long phone_number;
private String first_name;
private String last_name;
private byte[] password;
private int gender;
我的用户类中的字段。如果我将其转换为字符串,我读过它会弄乱密码,但如果我不这样做,那么该函数将不起作用,因为我传入的是 byte[] 而不是字符串。如何在保持此功能的同时保持 bcrypt 的安全性?
因为在 mysql 文档中它将 BINARY(60) 映射到 byte[] 这里 https://dev.mysql.com/doc/ndbapi/en/mccj-using-clusterj-mappings.html
每个人都说在这里将 bcrypt 存储为二进制(60) What column type/length should I use for storing a Bcrypt hashed password in a Database?
【问题讨论】:
【参考方案1】:您可以将哈希密码作为字符串返回。无需返回纯密码。事实上,没有人会期望你这样做。
这里有很多 Spring 魔术在幕后进行,并且有一个默认密码编码器/解码器,例如您可以使用PasswordEncoderFactories.createDelegatingPasswordEncoder()
获得密码编码器,它还有更多自定义选项。当用户登录时,将从UserDetails.getPassword()
返回的散列 密码与用户用于登录的密码的散列 版本进行比较。
我对 mysql 数据类型并不太熟悉,但如果让 Spring JPA 管理你的用户实体,并且它有一个用此 PasswordEncoderFactories.createDelegatingPasswordEncoder()
编码的密码字段,然后保存为字符串,那么数据库中的数据类型将只是一个 varchar 或 MySql 中调用的任何类型。此密码编码器默认使用 BCrypt,但您也可以将其配置为使用不同的哈希算法。
【讨论】:
【参考方案2】:根据第一个答案,您必须将来自数据库的哈希密码与用户使用passwordEncoder.matches(passwordFromUser, encodedPassword)
发送的密码进行比较。
您可以创建一个Bean
以轻松漂亮地将passwordEncoder
注入UserDetailsService
。
【讨论】:
【参考方案3】:将 bcrypt 存储为二进制(60) 主要是争论性的。根据我的经验,我会推荐varchar
。由于您在保存之前创建了密码哈希,因此以后将其与用户提供的密码进行比较不会有问题(显然您也必须对这个密码进行哈希处理)。
你说过:
如果我把它转换成我读过的字符串,它会弄乱密码
我认为这可以通过使用 mysql it self 的cast
来避免。虽然是绕圈子,但没有任何意义。
Spring 5 引入了DelegatingPasswordEncoder
。基于前缀标识符委托给另一个 PasswordEncoder 的密码编码器。
例如,您可以:
class DefaultPasswordEncoderFactories
//the passwords must be of form: bcryptxxx in the db
static PasswordEncoder createDelegatingPasswordEncoder()
String encodingId = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(encodingId, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
DelegatingPasswordEncoder delegatingPasswordEncoder = new DelegatingPasswordEncoder(encodingId, encoders);
delegatingPasswordEncoder.setDefaultPasswordEncoderForMatches(new BCryptPasswordEncoder(12));
return delegatingPasswordEncoder;
例如,这可以在DaoAuthenticationProvider
中轻松使用:
private final PasswordEncoder passwordEncoder = DefaultPasswordEncoderFactories.createDelegatingPasswordEncoder();
...
@Bean
public DaoAuthenticationProvider authenticationProvider()
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(ethUserDetailsService);
provider.setPasswordEncoder(this.passwordEncoder);
return provider;
检查以下链接,除了您提供的链接之外,它可能会有用 What data type to use for hashed password field and what length?
【讨论】:
以上是关于在 Spring Security 中,UserDetails 将密码存储为字符串,但我将其存储为 byte[]的主要内容,如果未能解决你的问题,请参考以下文章
Spring security:Spring security 如何在 SessionRegistry 中注册新会话?
在使用 Oauth、SAML 和 spring-security 的多租户的情况下从 spring-security.xml 中获取错误
spring boot 整合spring security中spring security版本升级的遇到的坑