我们有一个简单的操作符对象,它使用spring security来编码密码:
class Operator
transient springSecurityService
def siteService
String username
Site site
String password
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
现在我们要验证密码是否与运行时定义的正则表达式匹配。可以通过UI(即标准CRUD表单)创建操作符。我们为每个站点提供了不同的正则表达式。密码被覆盖密码覆盖的事实,我们不应该测试它,使其更具挑战性。
def beforeInsert() {
protected void encodePassword() {
String regexp = siteService.getInheritedValue(site, "operatorPasswordRegexp")
if (!password.matches(regexp) {
thrown new RuntimeException "invalid password format"
}
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
}
这部分有效,因为它会停止与正在创建的运行时找到的正则表达式不匹配的密码。问题是当我们希望操作员编辑表单用一个友好的验证消息突出显示密码字段时,它会引发异常,从而生成500错误页面。
static constraints = {
password password: true, blank:false, validator: { val, obj ->
if (obj.isDirty(val)) {
return true
}
String regexp = obj.siteService.getInheritedValue(obj.operates, "operatorPasswordRegexp")
if (regexp != null && regexp != "") {
return val.matches(regexp)
}
return true
}
这似乎有效,但保存总是无声地失败。我花了一些时间才意识到为什么 - 当你这样做时:
operator.password="valid1"
opertor.save(failonError:true)
不会抛出任何错误。即使删除failonError并检查返回值,它也始终为null(无错误)。但它并不能保证操作员的安全。
问题是beforeInsert正在将密码更新为当然没有通过验证器的编码版本(并且不应该),验证器在此时说不,并且保存默认失败。即为一次保存调用验证器两次。
问题是,如何让beforeInsert()代码不调用验证器,或者验证器忽略从beforeInsert调用?
答案 0 :(得分:1)
您可以使用这两种方法完成任务。
1:在encodePassword()中进行验证:不是抛出异常,而是向实例添加错误。我认为您的encodePassword()
函数位于同一个域中,因此使用this.errors
获取与其关联的错误对象。例如:
this.errors.rejectValue("password", "user.password.pattern.error")
有不同的rejectValue方法,这个方法接受message.properties文件中定义的字段名称和消息代码。
2:自定义验证器:
isDirty()
不是静态方法,使用自定义验证器中提供的obj调用它。 isDirty()
接受要检查的属性名称是否有肮脏而不是其值。
obj.isDirty(PropertyName)
约束是一个静态块,它无法直接访问您的服务。您需要使用静态上下文注入您的服务。
static SiteService siteService;
我建议使用自定义验证器。