【问题标题】:Password encoding with Spring Data REST使用 Spring Data REST 进行密码编码
【发布时间】:2015-07-27 10:36:16
【问题描述】:

我应该如何使用 Spring Data REST 自动对我的实体提交的纯密码字段进行编码?

我正在使用 BCrypt 编码器,我想在客户端通过 POST、PUT 和 PATCH 发送请求时自动对请求的密码字段进行编码。

@Entity
public class User {
  @NotNull
  private String username;
  @NotNull
  private String passwordHash;
  ...
  getters/setters/etc
  ...
}

首先我尝试使用@HandleBeforeCreate 和@HandleBeforeSave 事件侦听器来解决,但它的参数中的用户已经合并,所以我无法区分用户的新密码或旧密码哈希:

@HandleBeforeSave
protected void onBeforeSave(User user) {
    if (user.getPassword() != null) {
        account.setPassword(passwordEncoder.encode(account.getPassword()));
    }
    super.onBeforeSave(account);
}

是否可以在 setter 方法上使用 @Projection 和 SpEL?

【问题讨论】:

    标签: spring-data-rest


    【解决方案1】:

    你可以实现一个Jackson JsonDeserializer

    public class BCryptPasswordDeserializer extends JsonDeserializer<String> {
    
        public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
            ObjectCodec oc = jsonParser.getCodec();
            JsonNode node = oc.readTree(jsonParser);
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            String encodedPassword = encoder.encode(node.asText());
            return encodedPassword;
        }
    }
    

    并将其应用于您的 JPA 实体属性:

    // The value of the password will always have a length of 
    // 60 thanks to BCrypt
    @Size(min = 60, max = 60)
    @Column(name="password", nullable = false, length = 60)
    @JsonDeserialize(using = BCryptPasswordDeserializer.class )
    private String password;
    

    【讨论】:

    • 这是一个非常好的答案 - 对于这个问题。但是,这仅在将实体反序列化为 Java 对象时才有效。对于 REST api,这已经足够了,但是如果您的 api 也很流畅,或者以某种方式尝试使用 JPA 存储库在您的代码中保存实体,您将无法再从中受益。然后密码将按原样保留。知道我们如何实现后者吗?
    【解决方案2】:

    修改密码字段的setter方法即可,如下图:

    public void setPassword(String password) {
            PasswordEncoder encoder = new BCryptPasswordEncoder();
            this.password = encoder.encode(password);
        }
    

    参考: https://github.com/charybr/spring-data-rest-acl/blob/master/bookstore/src/main/java/sample/sdr/auth/bean/UserEntity.java

    【讨论】:

    • 它不会起作用,因为我不使用分离的 DTO,但我的应用程序与持久层共享实体,因此由 ORM(在本例中为 Hibernate)设置的密码哈希将被再次编码。跨度>
    • 但@javax.persistence.Access(AccessType.FIELD) 似乎解决了与密码设置器的休眠冲突。
    • 这可能不起作用,因为如果您的 AuthenticationProvider 也使用密码编码器(并且应该),您将遇到不匹配,因为 AuthenticationProvider 将尝试对已编码的密码,因此认证失败。虽然没有downvotes。别往心里放。很好的建议,但不会奏效。
    【解决方案3】:

    对@robgmills JsonDeserializer 解决方案的一些增强:

    • 在 Spring 5 中引入 DelegatingPasswordEncoder。更灵活,见spring docs
    • 不必每次反序列化时都创建PasswordEncoder
    • 一个大项目可能有多个JsonDeserializer - 最好让它们成为内部类。
    • 通常为获取请求编码隐藏密码。我用过@JsonProperty(access = JsonProperty.Access.WRITE_ONLY),见https://stackoverflow.com/a/12505165/548473

    对于 Spring Boot 代码如下:

    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        public static final PasswordEncoder PASSWORD_ENCODER = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
           auth.userDetailsService(userDetailsService()).passwordEncoder(PASSWORD_ENCODER);
        }
        ....
    
    public class JsonDeserializers {
        public static class PasswordDeserializer extends JsonDeserializer<String> {
            public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                ObjectCodec oc = jsonParser.getCodec();
                JsonNode node = oc.readTree(jsonParser);
                String rawPassword = node.asText();
                return WebSecurityConfig.PASSWORD_ENCODER.encode(rawPassword);
            }
        }
        ...
    
    @Entity
    public class User ...
    
        @Column(name = "password")
        @Size(max = 256)
        @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
        @JsonDeserialize(using = JsonDeserializers.PasswordDeserializer.class)
        private String password;
        ...
    

    【讨论】:

      猜你喜欢
      • 2016-05-16
      • 2015-07-26
      • 2016-01-23
      • 1970-01-01
      • 2012-03-07
      • 2020-09-21
      • 2018-12-10
      • 1970-01-01
      • 2017-02-21
      相关资源
      最近更新 更多