@valid参数校验使用,全方位多角度保证你的数据安全

如何优雅地进行参数校验

  • 写在前面
    • 前端校验
    • 后端校验
      • 一、普通的字段校验
        • 1.引入依赖
        • 2.在实体类entity需要检验的字段上加上注解
        • 3.开启校验,`如果只是加注解并不生效`
        • 4.统一返回错误信息和状态码
      • 二、分组校验
        • 1.创建分组接口
        • 2.在使用注解时,表名分组
        • 3.在controller层表名使用的分组
        • 4.简单测试一下
        • 5.注意事项
      • 三、自定义校验
        • 1.编写自定义注解
        • 2.编写自定义校验器
        • 3.编写配置文件
        • 4.我们测试一下
    • 最后

写在前面

再Javaweb的开发中,为了防止懂技术的人对数据库的恶意攻击,我们通常使用参数校验对无效数据进行筛选,Java生态下的@valid注解配置SpringBoot的使用可以方便快速的完成对数据校验的各种场景

同时数据校验分为前端校验和后端校验

前端校验

通常我们使用脚手架快速搭建页面,比如element-ui提供了可以参数校验的表单,官方文档有较为详细的使用教程,传送门,在此就不再重复,本文重点在于后端的参数校验
@valid参数校验使用,全方位多角度保证你的数据安全_第1张图片

后端校验

如果有人拿到了url地址,使用第三方测试工具比如postman就可以跳过前端页面的参数检验,所以为了数据库数据正确性,我们十分有必要对传来的数据在后端进行第二次校验

一、普通的字段校验

基本的使用步骤

1.引入依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

2.在实体类entity需要检验的字段上加上注解

常见的注解,这里只是列举一些

@NotEmpty:加了此注解的String类、Collection、Map、数组,是不能为null或者长度为0的(String Collection Map的isEmpty()方法)

@NotBlank:只用于String,不能为null且trim()之后size>0

@NotNull:不能为null,但可以为empty,没有Size的约束

@Pattern(value):val为指定的正则表达式

在要检验的字段上加上注解,同时指定如果不匹配的报错信息

在这里插入图片描述

3.开启校验,如果只是加注解并不生效

如图所示,列举一个简单的例子,刚刚我们在NewsVo的title中加上了@NotBlank注解,此时我们在方法中加入@Valid注解,这样便完成了参数校验的简单使用
@valid参数校验使用,全方位多角度保证你的数据安全_第2张图片
简单测试一下
在这里插入图片描述我们发现返回的信息十分复杂,我们希望可以在项目中统一返回错误信息

4.统一返回错误信息和状态码

我们需要定义一个异常捕获处理类,和一个状态码Enum枚举类来规范返回的数据

  • Enum枚举类
public enum CodeEnum {
// 根据自己的项目需求更改状态码,这里只是一个示范
    UNKNOW_EXCEPTION(10000,"系统未知错误"),
    VALID_EXCETIPON(10001,"参数格式校验错误");
    private int code;
    private String msg;
    CodeEnum(int code, String msg){
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

  • 异常处理类

@Slf4j
@RestControllerAdvice("com.cbj.db_work.controller") //表明需要处理异常的范围
public class GlobalExceptionHandler {

    @ExceptionHandler(value = MethodArgumentNotValidException.class) // 参数异常抛出的异常类型为MethodArgumentNotValidException,这里捕获这个异常
    // R为统一返回的处理类
    public R validExceptionHandler(MethodArgumentNotValidException e){
        System.out.println("数据异常处理");
        log.error("数据校验出现问题,异常类型:{}",e.getMessage(),e.getClass());
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach((item)->{
            String message = item.getDefaultMessage();
            // 获取错误的属性字段名
            String field = item.getField();
            map.put(field,message);
        });
        return R.error().code(CodeEnum.VALID_EXCETIPON.getCode()).message(CodeEnum.VALID_EXCETIPON.getMsg()).data("errorData",map);
    }
    
}

我们再测试一下,看看返回结果是不是我们希望的样子
@valid参数校验使用,全方位多角度保证你的数据安全_第3张图片所以至此我们完成了参数校验的统一返回处理情况

二、分组校验

假设这么一种情景:

我们使用同一个Vo类来传递save和update方法的数据,但对于id来说,通常有框架帮我们生成id,我们不需要传id此时需要使用注解@Null表名id数据必须为空

但对于update方法,我们必须传id才能进行update操作

所以同一个字段面对不同的场景不同需求就可以使用分组校验

基本的使用步骤

1.创建分组接口

在这里插入图片描述
这里并不需要实现编写什么代码,标明分类

2.在使用注解时,表名分组

	@Null(message = "无需传id",groups = AddGroup.class)
    @NotNull(message = "必须传入id",groups = UpdateGroup.class)
    private String id;

3.在controller层表名使用的分组

// 这里可以指定多个分组,只需要在{}添加多个class即可
@PostMapping("/addNews")
    public R addNews(@Validated({AddGroup.class}) @RequestBody NewsVo vo){
        boolean flag = newsService.addNews(vo);
        if(flag)
            return R.ok();
        return R.error();
    }

4.简单测试一下

我们在添加时,传入id值,看看返回结果
@valid参数校验使用,全方位多角度保证你的数据安全_第4张图片我们可以看到成功返回错误数据

5.注意事项

当我们在controller层指定分组后,属性上没有表名分组的校验还执行么?
@valid参数校验使用,全方位多角度保证你的数据安全_第5张图片如果所示,关于title的校验我们没有指定分组,我们测试一下,看看是否生效
在这里插入图片描述我们发现测试成功了,说明一旦开启了分组校验,就必须把所有的校验规则都指定组别,不然不生效

三、自定义校验

分析场景:假设我们有一个字段比如showStatus只能由0和1两个取值,我们可以使用正则,也可以自定义注解校验,这里我们展示如何使用自定义校验

即我们手动实现以下图示注解
在这里插入图片描述

实现步骤

1.编写自定义注解

// Target表示注解使用的范围
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
// 获取注解的时间,固定值
@Retention(RetentionPolicy.RUNTIME)
// 匹配的校验器,我们稍后编写,关联注解和校验器
@Constraint(validatedBy = {ListValueConstrainValidator.class})
@Documented
public @interface ListValue {
    // 错误信息去哪找,通常我们使用配置文件,稍后编写
    String message() default "{com.cbj.db_work.valid.ListValue.message}";
    // 支持分组校验
    Class<?>[] groups() default {};
    // 自定义负载信息
    Class<? extends Payload>[] payload() default {};
    // 指定参数,就是上图中指定的可取值的范围
    int []  vals() default {};
}

2.编写自定义校验器

// 实现ConstraintValidator接口,泛型值:<自定义注解类,被校验值的数据类型>
public class ListValueConstrainValidator implements ConstraintValidator<ListValue,Integer> {
    // 整体思路,使用set在initialize获得参数信息,在isValid方法中校验,成功true,失败false
    Set<Integer> set = new HashSet<>();
    // 初始化方法,可以得到详细信息
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        for (int val : vals) {
            set.add(val);
        }
    }

    /**
     * 校验是否匹配
     * @param value 就是需要校验的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(value);
    }
}

3.编写配置文件

在这里插入图片描述


com.cbj.db_work.valid.ListValue.message=必须提交指定的值

在配置文件中可以指定匹配错误时显示的信息,也可以message指定
在这里插入图片描述

4.我们测试一下

温馨提示:如果开启了分组校验,那么此注解必须指名组别
@valid参数校验使用,全方位多角度保证你的数据安全_第6张图片如果出现乱码:setting->file encoding中全部设为utf-8->重新创建配置文件

最后

以上就是关于参数校验的全部内容,我们在项目中可以优雅的进行参数校验,而不用使用一连串if-else,如果觉得文章写的不错,三连就是对博主最大的支持,肝了,xdm
@valid参数校验使用,全方位多角度保证你的数据安全_第7张图片

你可能感兴趣的:(spring,java,spring,spring,boot)