运行 在 Spring 引导数据 REST 中进行实体验证后 @HandleBeforeCreate

Running @HandleBeforeCreate after entity validation in Spring Boot Data REST

我正在使用 Spring 引导数据 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
}

使用存储库:

public interface UserRepository extends CrudRepository<User, Long> {}

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

@Configuration
public class CustomRestConfiguration extends SpringBootRepositoryRestMvcConfiguration {

    @Autowired
    private Validator validator;

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

}

并且仅在稍后将用户密码存储在数据库中之前对其进行哈希处理:

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

    private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

    @HandleBeforeCreate
    public void handleUserCreate(User user) {
         user.setPassword(passwordEncoder.encode(user.getPassword()));
    }
}

事实证明,验证是在密码散列之后执行的,结果由于 散列 密码太长而失败。

有没有什么方法可以让Spring先执行验证,然后再散列密码?我知道我可以自己编写一个控制器并以细粒度的方式指定所有内容,但我宁愿把它留作最后的选择。

当我在调试器中进行调查时,发现传入的实体按以下顺序处理:

  1. Spring 在 SpringValidatorAdapter::validate 中反序列化 JSON 时执行 bean 验证。这里的密码是明文的
  2. @HandleBeforeCreate 被调用并对密码进行哈希处理。
  3. JPA 在将实体保存到数据库之前执行实体验证。这里的密码已经过哈希处理,验证失败。在我的例子中(JPA 的 Hibernate 实现)验证是在 BeanValidationEventListener::validate.
  4. 中执行的

方案一(两阶段全验证)

我发现的一个解决方案是通过仅使用 @NotEmpty 来放宽对 password 字段的约束(这样两个验证阶段都通过了,并且仍然检查传入的 JSON emptiness/nullity) 并在 @HandleBeforeCreate 中执行原始密码的大小验证(并在需要时从那里抛出适当的异常)。

此解决方案的问题在于它需要我编写自己的异常处理程序。为了跟上 Spring Data REST 在错误响应主体方面设定的高标准,我将不得不为这个简单的案例编写大量代码。 here.

描述了执行此操作的方法

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

正如 Bohuslav Burghardt, it is possible to disable the second validation phase done by JPA. This way you can keep the min and max constrains and at the same time avoid writing any additional code. As always it's a trade-off between simplicity and safety. The way to disable JPA is described here 所暗示的那样。

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

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

此解决方案的唯一警告是 @Size(min = 5) 似乎不检查无效性,因此我不得不添加 @NotNull 来处理这种情况。总而言之,该字段被注释为:

@NotNull
@Size(min = 5)
private String password;