数据校验

目录

  • 数据校验
  • 1. 为什么需要数据校验
  • 2. 数据校验的解决
  • 使用Hibernate-validator进行校验
  • 1. 依赖
  • 2. 常用的注解
  • 3.@Valid和@Validated的区别
  • DEMO
  • 分组校验

1. 为什么需要数据校验

虽然我们在前台js进行了拦截,比如submit总体校验一遍,或者每个form控件blur失去焦点的时候进行了校验,但是

我们服务器接口可能被服务器通过代码(http-client)访问,或者其他的方式跳过浏览器js的校验逻辑,如果后台不进行

校验,那么可能会带来严重的安全问题:比如sql注入,XXS攻击等等安全漏洞。

2. 数据校验的解决

使用Hibernate-validator进行校验
1. 依赖
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
 </dependency>
2. 常用的注解

注解

注解的元素类型

描述

@AssertFalse

Boolean、boolean

被注解的元素必须为False

@AssertTrue

Boolean、boolean

被注解的元素必须为True

@DecimalMax

BigDecimal、BigInteger、CharSequence、byte、short、int、long以及它们各自的包装类

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin

BigDecimal、BigInteger、CharSequence、byte、short、int、long以及它们各自的包装类

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Digits

BigDecimal、BigInteger、CharSequence、byte、short、int、long以及它们各自的包装类

被注释的元素必须是一个数字,其值必须在可接受的范围内

@Email

CharSequence

被注释的元素必须是电子邮箱地址

@Future

Java.util.Date、Java.util.Calender以及java.time包下的时间类

被注释的元素必须是一个将来的日期

@FutureOrPresent

Java.util.Date、Java.util.Calender以及java.time包下的时间类

被注释的元素必须是当前时间或之后一个时间

@Max

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注释的元素必须是一个数字,其值必须小于等于指定的最大值,注意如果@Max所注解的元素是null,则@Max注解会返回true,所以应该把@Max注解和@NotNull注解结合使用

@Min

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注释的元素必须是数字,并且值要大于或等于给定的值。注意如果@Min所注解的元素是null,则@Min注解会返回true,即也会通过校验,所以应该把@Min注解和@NotNull注解结合使用

@Negative

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注解的元素必须是负数

@NegativeOrZero

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注解的元素必须是负数或0

@NotBlank

CharSequence

被注解的元素必须不为null并且至少有一个非空白字符

@NotEmpty

CharSequence、Collection、Map、Array

被注解的字符串不为null或空字符串,被注解的集合或数组不为空。和@NotBlank相比,一个空字符串在@NotBlank中验证不通过,但是在@NotEmpty中验证可以通过

@NotNull

任意类型

被注解的元素不能为null

@Past

Java.util.Date、Java.util.Calender以及java.time包下的时间类

被注解的元素必须是一个过去的日期

@PastOrPresent

Java.util.Date、Java.util.Calender以及java.time包下的时间类

被注解的元素必须是一个过去的日期或者当前日期

@Pattern

CharSequence

被注解的元素必须符合指定正则表达式

@Positive

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注解的元素必须是正数

@PositiveOrZero

BigDecimal、BigInteger、byte、short、int、long以及它们各自的包装类

被注解的元素必须是正数或0

@Size

CharSequence、Collection、Map、Array

被注解的字符串长度,集合或者数组的大小必须在指定的范围内

3.@Valid和@Validated的区别
  1. 作用范围:
    @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
    @Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上,不支持嵌套检测
  2. 来源:
    @Valid:使用Hibernate validation的时候使用
    @Validated:只用Spring Validator校验机制使用
  3. 功能:
    @Valid:支持嵌套检测,不支持分组检测
    @Validated:不支持嵌套检测,支持分组检测

DEMO

package com.asher.springboot.service.vo;

import javax.validation.Valid;
import javax.validation.constraints.*;

/**
 * \* @author: AsherWu
 * \* Date: 2022/4/21
 * \* Description: 学生信息VO 新增或者修改学生信息的时候接收参数
 * \
 */
public class StudentVO {

    @NotBlank(message = "学生姓名不能为空!")
    private String stuName;

    @Max(value = 30, message = "学生年龄不能超过30!")
    @Min(value = 10, message = "学生年龄不能超过10!")
    private Integer age;

    @NotNull(message = "学生性别不能为空!")
    private Integer sex;

    @NotBlank(message = "学生地址不能为空!")
    private String address;

    @Valid
    private ParentVO parentVO;

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public ParentVO getParentVO() {
        return parentVO;
    }

    public void setParentVO(ParentVO parentVO) {
        this.parentVO = parentVO;
    }

    @Override
    public String toString() {
        return "StudentVO{" +
                "stuName='" + stuName + '\'' +
                ", age=" + age +
                ", sex=" + sex +
                ", address='" + address + '\'' +
                ", parentVO=" + parentVO +
                '}';
    }
}
package com.asher.springboot.service.vo;

import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;

/**
 * \* @author: AsherWu
 * \* Date: 2022/4/21
 * \* Description: 家长信息VO 与学生信息关联
 * \
 */
public class ParentVO {

    @NotBlank(message = "家长姓名不能为空!")
    private String parName;

    @Max(value = 80, message = "家长年龄不能超过80!")
    @Min(value = 30, message = "家长年龄不能低于30!")
    private Integer parAge;

    @NotBlank(message = "家长联系方式不能为空!")
    private String phone;

    public String getParName() {
        return parName;
    }

    public void setParName(String parName) {
        this.parName = parName;
    }

    public Integer getParAge() {
        return parAge;
    }

    public void setParAge(Integer parAge) {
        this.parAge = parAge;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "ParentVO{" +
                "parName='" + parName + '\'' +
                ", parAge=" + parAge +
                ", phone='" + phone + '\'' +
                '}';
    }
}
package com.asher.springboot.controller;

import com.asher.springboot.core.domain.AjaxResult;
import com.asher.springboot.service.vo.StudentVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.bind.BindResult;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.List;

/**
 * \* @author: AsherWu
 * \* Date: 2022/4/21
 * \* Description: 测试valid
 * \
 */
@RestController
@RequestMapping("valid")
public class ValidController {
    private static final Logger logger = LoggerFactory.getLogger(ValidController.class);

    @PostMapping("addStudent")
    public AjaxResult testValid(@RequestBody @Valid StudentVO studentVO, BindingResult bindResult) {
      
        logger.info("学生信息,{}",studentVO);
        final List<ObjectError> allErrors = bindResult.getAllErrors();
        if(!CollectionUtils.isEmpty(allErrors)){
            logger.error(allErrors.get(0).getDefaultMessage());
        }
        return AjaxResult.success();
    }
}

分组校验

在实际的开发中,新增表单和修改表单可能使用同一个类接收参数,但是我们在新增的时候需要对部分字段进行校验,在修改的时候对另外部分的字段进行校验,如果说新增和修改使用不同的类接收参数则不会有问题,但是更简便的做法就是进行分组校验

  1. 定义分组:首先就是定义分组,创建接口,例如Addgroup接口和UpdateGroup接口分别表示新增的时候需要校验和更新的时候需要校验
  2. 添加分组:在参数封装类的元素上添加分组例如,表示在新增的时候对学生年龄的最小值和最大值,非空进行校验
@Max(value = 30, message = "学生年龄不能超过30!",groups = {AddGroup.class})
@Min(value = 10, message = "学生年龄不能超过10!",groups = {AddGroup.class})
@NotNull(message = "学生年龄不能为空!",groups = {AddGroup.class})
private Integer age;
  1. 最重要的一点是在接口接收参数的校验注解中添加
@PostMapping("addStudent")
public AjaxResult testValid(@RequestBody @Validated(value = {AddGroup.class}) StudentVO studentVO, BindingResult bindResult) {
  // 如果接口层接收BindingResult校验的结果,则校验出错后不会抛出异常,由开发者自已决定相应的操作
  logger.info("学生信息,{}",studentVO);
  final List<ObjectError> allErrors = bindResult.getAllErrors();
  if(!CollectionUtils.isEmpty(allErrors)){
    logger.error(allErrors.get(0).getDefaultMessage());
  }
  return AjaxResult.success();
}

因为@Valid不支持分组校验,所以我们使用@Validated进行校验,这样我们就完成了一个简单的校验,如果接口参数指定的校验的分组,则不会对没有添加分组的元素进行校验,例如

@Max(value = 30, message = "学生年龄不能超过30!",groups = {AddGroup.class})
@Min(value = 10, message = "学生年龄不能超过10!",groups = {AddGroup.class})
@NotNull(message = "学生年龄不能为空!",groups = {AddGroup.class})
private Integer age;

@NotNull(message = "学生性别不能为空!")
private Integer sex;

如果接口指定了@Validated(value = {AddGroup.class}),则不会对sex进行校验

如果接口没有添加分组@Validated,则只校验sex,不会对age进行校验