在 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>
我要做的是首先验证POST
ed 用户:
@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 的字段