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

时间:2015-10-11 12:34:20

标签: java spring validation 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
}

使用存储库:

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

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

@Configuration
public class CustomRestConfiguration extends SpringBootRepositoryRestMvcConfiguration {

    @Autowired
    private Validator validator;

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

}

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

@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 个答案:

答案 0 :(得分:3)

正如我在调试器中调查的那样,结果是传入的实体按以下顺序处理:

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

    解决方案1(两个阶段都完全验证)

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

    这个解决方案的问题是它需要我编写自己的异常处理程序。为了跟上Spring Data REST在错误响应体方面设置的高标准,我必须为这个简单的案例编写大量代码。这样做的方法是here

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

    Bohuslav Burghardt所示,可以禁用JPA完成的第二个验证阶段。这样,您可以保持最小和最大约束,同时避免编写任何其他代码。一如既往,它是简单性和安全性之间的权衡。有关禁用JPA的方法,请参阅here

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

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

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

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