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

Posted

技术标签:

【中文标题】在 Spring Boot Data REST 中进行实体验证后运行 @HandleBeforeCreate【英文标题】:Running @HandleBeforeCreate after entity validation in Spring Boot Data REST 【发布时间】:2016-01-08 23:32:29 【问题描述】:

我正在使用 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> 

我要做的是首先验证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 先执行验证,然后才对密码进行哈希处理?我知道我可以自己编写一个控制器并以细粒度的方式指定所有内容,但我宁愿把它作为最后的手段。

【问题讨论】:

您使用的是什么版本的 Spring Boot/Data REST?我刚刚在我的应用程序上对此进行了测试,当我在 Repo 处理程序和SizeValidatorForCharSequence 中放置断点时,验证器中的那个在处理程序之前被命中,所以对我来说它按预期工作。我正在使用 Spring Boot 1.2.5 @BohuslavBurghardt 我在 Spring Boot 1.2.6.RELEASE 上,它管理所有其他依赖版本。我真的不知道如何在验证器上放置一个断点,因为我自动装配了 JPA 创建的一个(我想)。它发生在幕后某处。不过会尝试这样做 @Wojtek,在 bean 验证成功后(在它持久化实体之前),我是否可以使用其他任何钩子来获得控制? @masT 这不正是您需要的吗?它在验证之后和持久化之前被调用。还是您的意思是在 JPA 进行实体验证之后?我已经有一段时间没有使用 Spring 或 JPA 了,所以我真的无法回答你的问题。 @Wojtek,就我而言,这不是我需要执行的验证,而是为其他一些属性提供值(从另一个存储库中获取),因此寻找任何钩子验证后做这个处理。 【参考方案1】:

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

    Spring 在反序列化 SpringValidatorAdapter::validate 中的 JSON 时执行 bean 验证。这里的密码是纯文本的。 调用@HandleBeforeCreate 并对密码进行哈希处理。 JPA 在将实体保存到数据库之前执行实体验证。这里的密码已经散列,验证失败。就我而言(JPA 的 Hibernate 实现),验证是在 BeanValidationEventListener::validate 中执行的。

解决方案 1(两个阶段的全面验证)

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

这个解决方案的问题是它需要我编写自己的异常处理程序。为了跟上 Spring Data REST 设置的关于错误响应主体的高标准,我必须为这个简单的案例编写大量代码。执行此操作的方法描述为here。

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

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

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

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

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

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

【讨论】:

有趣的阅读。它对我有用的原因可能是因为我通过 application.properties (spring.jpa.properties.javax.persistence.validation.mode=none) 禁用了 Hibernate 验证。你也可以这样做,那么只会执行第 1 步反序列化期间的验证。 是的,我读过它,但我真的不想放弃所有 JPA 验证优点 :) 将其添加为另一种可能的解决方案。感谢您的帮助 Bohuslav! 用正则表达式验证字符串怎么样?

以上是关于在 Spring Boot Data REST 中进行实体验证后运行 @HandleBeforeCreate的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Spring Boot Data Rest 中启用 CORS

Spring Boot + Spring Data Rest Repositories 中使用 @CrossOrigin 注释的 CORS 问题

如何在 Spring Boot 中使用带有 Bearer Token 和 form-data 的 Rest Template 调用 REST Api

初入spring boot(八 )Spring Data REST

在 Spring Boot + Spring Data Rest 中反序列化时忽略带有 @JsonProperty 的字段

您如何保护 Spring Boot / Spring-Data Rest 以便用户只能访问他自己的实体