数据校验
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以及它们各自的包装类 | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
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的区别
-
作用范围:
@Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
@Validated:可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上,不支持嵌套检测 -
来源:
@Valid:使用Hibernate validation的时候使用
@Validated:只用Spring Validator校验机制使用
-
功能:
@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();
}
}
分组校验
在实际的开发中,新增表单和修改表单可能使用同一个类接收参数,但是我们在新增的时候需要对部分字段进行校验,在修改的时候对另外部分的字段进行校验,如果说新增和修改使用不同的类接收参数则不会有问题,但是更简便的做法就是进行分组校验
- 定义分组:首先就是定义分组,创建接口,例如
Addgroup
接口和UpdateGroup
接口分别表示新增的时候需要校验和更新的时候需要校验 - 添加分组:在参数封装类的元素上添加分组例如,表示在新增的时候对学生年龄的最小值和最大值,非空进行校验
@Max(value = 30, message = "学生年龄不能超过30!",groups = {AddGroup.class})
@Min(value = 10, message = "学生年龄不能超过10!",groups = {AddGroup.class})
@NotNull(message = "学生年龄不能为空!",groups = {AddGroup.class})
private Integer age;
- 最重要的一点是在接口接收参数的校验注解中添加
@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
进行校验