在Spring Boot Data REST中进行实体验证后运行@HandleBeforeCreate

Woj*_*tek 9 java validation spring spring-data-rest

我正在使用Spring Boot Data REST来保存我的User实体

@Entity
public class User {

    @Id
    @GeneratedValue
    private long id;

    @NotEmpty
    private String firstName;

    @NotEmpty
    private String lastName;

    @NotEmpty
    private String email;

    @Size(min = 5, max = 20)
    private String password;

    // getters and setters
}
Run Code Online (Sandbox Code Playgroud)

使用存储库:

public interface UserRepository extends CrudRepository<User, Long> {}
Run Code Online (Sandbox Code Playgroud)

我想要做的是首先验证POSTed用户:

@Configuration
public class CustomRestConfiguration extends SpringBootRepositoryRestMvcConfiguration {

    @Autowired
    private Validator validator;

    @Override
    protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
        validatingListener.addValidator("beforeCreate", validator);
    }

}
Run Code Online (Sandbox Code Playgroud)

并且只有在将用户密码存储到数据库之前才对其进行哈希处理:

@Component
@RepositoryEventHandler(User.class)
public class UserRepositoryEventHandler {

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @HandleBeforeCreate
    public void handleUserCreate(User user) {
         user.setPassword(passwordEncoder.encode(user.getPassword()));
    }
}
Run Code Online (Sandbox Code Playgroud)

事实证明,验证是在密码散列后执行的,因此由于散列密码太长而失败.

有没有办法指示Spring首先执行验证,然后才对密码进行哈希处理?我知道我可以自己编写一个控制器,并以细粒度的方式指定所有内容,但我宁愿把它作为我的最后手段.

Woj*_*tek 3

As I investigated in the debugger it turned out that an incoming entity is processed in the following order:

  1. Spring performs bean validation when deserializing JSON in SpringValidatorAdapter::validate. The password here is in plain text.
  2. @HandleBeforeCreate is invoked and the password is hashed.
  3. JPA performs entity validation before saving it in the DB. The password here is already hashed and validation fails. In my case (Hibernate implementation of JPA) the validation was performed in BeanValidationEventListener::validate.

Solution 1 (full validation in both phases)

我发现的一种解决方案是password通过仅使用@NotEmpty(以便两个验证阶段都通过并且仍然检查传入的 JSON 是否为空/无效)来放松对该字段的约束,并执行原始密码的大小验证@HandleBeforeCreate(并抛出适当的异常如果需要的话就在那里)。

该解决方案的问题在于它要求我编写自己的异常处理程序。为了跟上 Spring Data REST 在错误响应主体方面设定的高标准,我必须为这一简单的情况编写大量代码。此处描述了执行此操作的方法。

解决方案 2(没有 JPA 实体验证的 Spring bean 验证)

正如Bohuslav Burghardt所暗示的,可以禁用 JPA 完成的第二个验证阶段。这样您就可以保留最小和最大约束,同时避免编写任何额外的代码。一如既往,这是简单性和安全性之间的权衡。这里描述了禁用 JPA 的方法。

解决方案 3(仅保留最小密码长度限制)

另一个解决方案(至少在我的情况下有效)是不限制最大密码长度。这样,在第一个验证阶段,检查密码是否太短,在第二阶段,每次都有效验证(因为加密的密码已经足够长)。

这个解决方案的唯一警告是,它@Size(min = 5)似乎没有检查无效性,所以我必须添加@NotNull来处理这种情况。总而言之,该字段注释为:

@NotNull
@Size(min = 5)
private String password;
Run Code Online (Sandbox Code Playgroud)