【问题标题】:Spring Boot custom validator doesn't return to ControllerSpring Boot 自定义验证器不会返回到 Controller
【发布时间】:2021-03-26 10:17:44
【问题描述】:

我正在开发基本的重置密码流程。这是我的 DTO:

@Getter
@Setter
@FieldsValueMatch(field = "password", fieldMatch = "confirmPassword", message = "Passwords do not match")
public class PasswordResetForm {
    @Size(min = 8, message = "Password needs to have at least 8 characters")
    private String password;
    private String confirmPassword;
}

有FieldsValueMatch注解:

@Constraint(validatedBy = FieldsValueMatchValidator.class)
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldsValueMatch {
    String message() default "Fields values don't match!";

    String field();

    String fieldMatch();

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

和验证器:

public class FieldsValueMatchValidator implements ConstraintValidator<FieldsValueMatch, Object> {

    private String field;
    private String fieldMatch;

    @Override
    public void initialize(FieldsValueMatch constraintAnnotation) {
        this.field = constraintAnnotation.field();
        this.fieldMatch = constraintAnnotation.fieldMatch();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        Object fieldValue = new BeanWrapperImpl(value).getPropertyValue(field);
        Object fieldMatchValue = new BeanWrapperImpl(value).getPropertyValue(fieldMatch);

        if (fieldValue != null) {
            return fieldValue.equals(fieldMatchValue);
        } else {
            return fieldMatchValue == null;
        }
    }
}

这是我的控制器:

@Controller
public class ResetPasswordController {

    @ModelAttribute("passwordResetForm")
    public PasswordResetForm passwordResetForm() {
        return new PasswordResetForm();
    }

    @GetMapping("/reset-password")
    public String showResetPasswordForm(final Model model) {
        return "reset-password";
    }

    @PostMapping("/reset-password-result")
    public String resetPassword(@Valid final PasswordResetForm passwordResetForm,
                                final BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
        return "reset-password";
    }
        // reset password logic
        return "redirect:/reset-password-success";
    }
}

以及 Thymeleaf 页面的一部分:

<form th:action="@{/reset-password-result}" th:object="${passwordResetForm}" method="post">
    <div>
        <div class="input-group">
            <input id="password"
                   class="form-input"
                   placeholder="Set a new password"
                   type="password"
                   th:field="*{password}"/>
        </div>
        <div th:if="${#fields.hasErrors('password')}" th:errors="*{password}"></div>
    </div>
    <div>
        <div class="input-group">
            <input id="confirmPassword"
                   class="form-input"
                   placeholder="Re-type a new password"
                   type="password"
                   th:field="*{confirmPassword}"/>
        </div>
        <div th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}"></div>
    </div>
    <div class="form-group">
        <button type="submit" class="form-btn">SET</button>
    </div>
</form>

现在,当我在两个输入中输入不同的密码时,我在终端中收到以下消息:

2021-03-26 11:13:39.315  WARN 1340 [nio-8080-exec-7] 
s.w.s.h.AbstractHandlerExceptionResolver : Resolved 
[org.springframework.validation.BindException: 
org.springframework.validation.BeanPropertyBindingResult: 1 errors
Error in object 'passwordResetForm': codes 
[FieldsValueMatch.passwordResetForm,FieldsValueMatch]; arguments 
[org.springframework.context.support.DefaultMessageSourceResolvable: codes [passwordResetForm.,]; arguments []; default message [],password,confirmPassword]; default message [Passwords do not match]]

和即时 400 结果代码。我在控制器中的resetPassword 方法没有被调用,所以我的 Thymeleaf 页面我得到了 Whitelabel 错误页面。当我输入的密码少于 8 个字符时,也会发生同样的情况。我做错了什么?

感谢您的帮助!

【问题讨论】:

  • 你试过not把valid放在PasswordResetForm上吗?
  • 是的,在这种情况下验证不起作用
  • 验证在其他地方对您有用吗?也许你面对的是this issue?
  • FWIW,您的代码在粘贴到全新的 Spring Boot 2.4.4 项目时似乎可以工作。

标签: java spring spring-boot spring-mvc thymeleaf


【解决方案1】:

您将@FieldsValueMatch 定义为类级别约束。可能是生成的默认约束违规导致了问题,因为在这种情况下,没有为创建的约束违规指定显式属性路径。

当我输入的密码少于 8 个字符时,也会发生同样的情况。

不管其他字段级验证 (= @Size),@FieldsValueMatch 在任何情况下都会执行,这可能就是您仍然面临同样问题的原因。

因此,调整 FieldsValueMatchValidator 实现 - 通过为创建的约束违规设置属性路径并提供自定义错误消息 - 应该可以解决问题:

public class FieldsValueMatchValidator implements 
    ConstraintValidator<FieldsValueMatch, Object> {
    // ...
    private String message;

    @Override
    public void initialize(FieldsValueMatch constraintAnnotation) {
        // ...
        this.message = constraintAnnotation.message();
    }

    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        //...
        boolean valid;

        if (fieldValue != null) {
            valid = fieldValue.equals(fieldMatchValue);
        } else {
            valid = fieldMatchValue == null;
        }

        if (!valid){
            context.buildConstraintViolationWithTemplate(this.message) // setting the custom message
                    .addPropertyNode(this.field) // setting property path
                    .addConstraintViolation() // creating the new constraint violation 
                    .disableDefaultConstraintViolation() // disabling the default constraint violation
                    ;
        }

        return valid;
    }
}

【讨论】:

    【解决方案2】:

    尝试在ResetPasswordController 类上添加@Validated 注释(@Controller 注释之前/之后)。这应该启用验证

    【讨论】:

    • Valid 应该也可以,这不是解决方案
    【解决方案3】:

    检查您的方法参数序列。

    Method Arguments

    您必须在经过验证的方法参数之后立即声明 Errors 或 BindingResult 参数。

    因此,如果您的代码实际上如下所示,您的问题将会重现。

        @PostMapping("/reset-password-result")
        public String resetPassword(@Valid PasswordResetForm passwordResetForm, Model model,
            BindingResult bindingResult) {
            if (bindingResult.hasErrors()) {
                return "reset-password";
            }
            // reset password logic
            return "redirect:/reset-password-success";
        }
    

    【讨论】:

    • 与我的代码相比有什么不同?只有这里完全不需要的模型参数
    【解决方案4】:

    感谢所有回复。无缘无故,它刚刚开始工作,我这边没有任何改变......我不知道发生了什么,也许我需要重新启动我的网络浏览器或类似的东西......

    【讨论】:

      猜你喜欢
      • 2020-10-25
      • 2021-04-21
      • 2018-05-07
      • 1970-01-01
      • 1970-01-01
      • 2019-11-19
      • 1970-01-01
      • 2017-04-09
      • 2020-12-05
      相关资源
      最近更新 更多