在 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 【问题描述】:

我目前在 UserDetailsS​​erviceImpl 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下如何配置bean

Spring security:Spring security 如何在 SessionRegistry 中注册新会话?

在使用 Oauth、SAML 和 spring-security 的多租户的情况下从 spring-security.xml 中获取错误

spring boot 整合spring security中spring security版本升级的遇到的坑

如何在 spring-security 5.2 中增加 RemoteJWKSet 缓存 TTL

Spring Security系列教程解决Spring Security环境中的跨域问题