一,为什么要做参数验证?

永远不要相信我们在后端接收到的数据,

1,防止别人通过接口乱刷服务:有些不怀好意的人或机构会乱刷我们的服务,例如:短信接口,

相信大家可能很多人在工作中遇到过这种情况

2,防止sql注入等行为:如果对数据会行严格的验证,可以过滤掉大量的攻击行为

3,防止客户端出错后的生成数据错误

所以,后端必须进行参数校验,

即使前端已经校验过,因为我们不能保证我们收到的请求都是由我们的前端程序发出

说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/architectforest

对应的源码可以访问这里获取: https://github.com/liuhongdi/

说明:作者:刘宏缔 邮箱: 371125307@qq.com

二,演示项目的相关信息

1,演示项目的地址:

https://github.com/liuhongdi/validator

2,演示项目的原理:

演示了三种情况:

直接针对controller的参数校验

针对一个表单校验

针对通用的参数用拦截器进行校验

3,项目结构

如图:

三, 如何使用validation库?

1,pom.xml中引入validation

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

2,validation有哪些现成的注解可用?

2.1 空检查
@Null 验证对象是否为空
@NotNull 验证对象不为空
@NotBlank 验证字符串不为空或不是空字符串
@NotEmpty 验证对象不为Null,或者集合不为空

2.2 长度检查
@Size 验证对象长度,支持字符串,集合
@Length 验证字符串大小(于 org.hibernate.validator.constraints 包中)

2.3 数值检查
@Min 验证数字是否大于等于指定值
@Max 验证数字是否小于等于指定值
@Digits 验证数字是否符合指定的格式
@Range 验证数字是否在指定的范围内
@Negative 验证数字是否为负数
@NegativeOrZero 验证数字是否小于等于0
@Positive 验证数字是否为正数
@PositiveOrZero验证数字是否大于等于0
@DecimalMin 验证数字是否大于指定值
@DecimalMax 验证数字是否小于等于指定值 2.4 时间检查
@Future 检查时间是否晚于现在
@FutureOrPresent 检查时间是否非早于现在
@Past 检查时间是否早于现在
@PastOrPresent 检查时间是否非晚于现在

2.5 其他
@Email 检查是否一个合法的邮箱地址
@Pattern 检查是否符合指定的正则规则

3,如何配置使validator匹配到一个错误时立即返回,而不是等所有字段验证完?

ValidatorConfig.java

@Configuration
public class ValidatorConfig {
/*
*@author:liuhongdi
*@date:2020/7/12 上午10:48
*@description:遇到第一个错误后立即返回,而不是遍历完全部错误
*/
@Bean
public Validator validator() {
ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
.configure()
.addProperty("hibernate.validator.fail_fast", "true") //快速验证模式,有第一个参数不满足条件直接返回
.buildValidatorFactory();
return validatorFactory.getValidator();
} @Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor postProcessor = new MethodValidationPostProcessor();
postProcessor.setValidator(validator());
return postProcessor;
}
}

四,例一:自定义一个validator

1,controller中直接用注解验证参数:(适用于参数少的情况)

@Validated
@Controller
@RequestMapping("/home")
public class HomeController {

    @GetMapping("/age")
@ResponseBody
public ResultUtil age(@Min(value = 10,message = "年龄最小为10")@Max(value = 100,message = "年龄最大为100") @RequestParam("age") Integer age) {
return ResultUtil.success("this is age");
}

说明:使用以下url可以测试效果

http://127.0.0.1:8080/home/age
http://127.0.0.1:8080/home/age?age=1
http://127.0.0.1:8080/home/age?age=60
http://127.0.0.1:8080/home/age?age=101

2, 自定义validator要用的注解

功能说明:传递的v参数必须是我们在常量中定义的值

VersionValid.java:用来定义注解

//验证版本号是否符合系统定义
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
@Constraint(validatedBy = VersionValidator.class)
public @interface VersionValid {
//用value传递的值
//String values();
//version无效时的提示内容
String message() default "version必须属于预定义的值"; Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}

VersionValidator.java  (override这个方法:isValid,用来验证数据是否合法)

public class VersionValidator implements ConstraintValidator<VersionValid,Object> {

    //预定义传递的值
//private String values;
@Override
public void initialize(VersionValid versionValidator) {
//this.values = versionValidator.values();
} //version是否符合定义
@Override
public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
// 切割获取值
String[] value_array = Constants.APP_VERSION_LIST.split(",");
Boolean isFlag = false;
for (int i = 0; i < value_array.length; i++){
// 存在一致就跳出循环
if (value_array[i] .equals(value)){
isFlag = true; break;
}
}
return isFlag;
}
}

在controller中调用VersionValid注解

    @GetMapping("/home")
@ResponseBody
public ResultUtil home(@VersionValid(message = "v取值错误")@RequestParam("v") String version) {
return ResultUtil.success("this is home");
}

3,测试效果:

错误

http://127.0.0.1:8080/home/home
http://127.0.0.1:8080/home/home?v=1

正确:

http://127.0.0.1:8080/home/home?v=1.0

五,例二:针对一个表单中多字段的validator

1,说明:有多个字段要验证的表单,

如果写到controller中会使代码过于庞大而不便管理

通常我们会定义一个专门类进行处理

这里要说明的是:类中的验证是针对每个字段的,

如果我们要比较类中两个或以上的字段值,(例如:注册页面:验证两次输入的密码是否一致)

则需要针对整个类定义一个validator

2,定义一个注解:

PassValid.java

//用来验证类中多个字段的validator的注解
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PassValidator.class)
@Documented
public @interface PassValid {
//报错信息
String message() default "confirmPassword:两次输入密码需一致";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
//密码字段
String password();
//确认密码字段
String password_confirm();
}

PassValidator.java 判断两次输入的密码是否一致的validator

//validator,判断两个密码是否一致
public class PassValidator implements ConstraintValidator<PassValid, Object> { //密码
private String passFieldName;
//确认密码
private String confirmFieldName; @Override
public void initialize(final PassValid constraintAnnotation) {
passFieldName = constraintAnnotation.password();
confirmFieldName = constraintAnnotation.password_confirm();
} @Override
public boolean isValid(final Object src, final ConstraintValidatorContext context) {
BeanWrapperImpl wrapper = new BeanWrapperImpl(src);
Object passObj = wrapper.getPropertyValue(passFieldName);
Object confirmObj = wrapper.getPropertyValue(confirmFieldName);
return passObj != null && passObj.equals(confirmObj);
}
}

应用上面创建的validator,实现针对整个表单的验证

@PassValid(password = "password", password_confirm = "confirmPassword")
public class UserVo { @Size(min = 2,max = 10,message = "name:姓名长度必须为1到10")
private String name;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
} //@Range(min=10, max=100,message = "年龄需位于10到100之间")
@Min(value = 10,message = "age:年龄最小为10")
@Max(value = 100,message = "age:年龄最大为100")
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
} @NotNull(message = "mobile:手机号码不能为空")
@Size(min = 11, max = 11, message = "mobile:手机号码必须为11位")
@Pattern(regexp="^[1]\\d{10}$", message="mobile:手机号码格式错误")
private String mobile;
public String getMobile() {
return this.mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
} @NotBlank(message = "email:邮箱不能为空")
@Email(message = "email:邮箱格式错误")
private String email;
public String getEmail() {
return this.email;
}
public void setEmail(String email) {
this.email = email;
} @NotBlank(message = "password:密码不能为空")
String password;
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
} @NotBlank(message = "confirmPassword:确认密码不能为空")
String confirmPassword;
public String getConfirmPassword() {
return this.confirmPassword;
}
public void setConfirmPassword(String confirmPassword) {
this.confirmPassword = confirmPassword;
} // @Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正确")
//@Pattern(regexp="^(\\d{18,18}|\\d{15,15}|(\\d{17,17}[x|X]))$", message="身份证格式错误") }

在controller中应用:

    @GetMapping("/user")
//@ResponseBody
public String user() {
return "user/user";
} @PostMapping("/usersaveed")
@ResponseBody
//public ResultUtil usersaveed(@Validated UserVo userVo) {
public ResultUtil usersaveed(@Validated UserVo userVo) {
System.out.println("----------email:"+userVo.getEmail());
return ResultUtil.success("this is in usersaveed");
}

3,测试效果:

http://127.0.0.1:8080/home/user

如图:

六,例三:针对通用的参数,用interceptor做校验

1,说明:

我们在访问接口时,通常有一些通用的参数要传递,

例如:

appid:通常是所在平台

version:客户端的版本

uuid:客户端的唯一id

2,定义interceptor要匹配的地址

DefaultMvcConfig.java

@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class DefaultMvcConfig implements WebMvcConfigurer { @Resource
private ValidatorInterceptor validatorInterceptor; /**
* 添加Interceptor
* 检验参数不能全部覆盖,因为可能有供第三方访问的接口地址,例如支付的回调接口
* 所以需要把不用的排除掉
*/ @Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(validatorInterceptor)
.addPathPatterns("/**") //所有请求都需要进行报文签名sign
.excludePathPatterns("/home/age**","/home/home**","/home/user**","/js/**","/"); //排除age/home...url
} }

ValidatorInterceptor.java :实现通用参数验证的interceptor

@Component
public class ValidatorInterceptor implements HandlerInterceptor {
/*
*@author:liuhongdi
*@date:2020/7/1 下午4:00
*@description:检查通用的变量是否存在,是否合法
* @param request:请求对象
* @param response:响应对象
* @param handler:处理对象:controller中的信息 *
* *@return:true表示正常,false表示被拦截
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//检查appid是否存在?
String appId = request.getParameter("appid");
if (appId == null) {
throw new BusinessException(ResponseCode.ARG_NO_APPID);
}
//appid是否符合定义
if (!Constants.APP_ID_LIST.contains(appId)) {
throw new BusinessException(ResponseCode.ARG_APPID_VALID);
}
//version参数是否存在
String version = request.getParameter("version");
if (version == null) {
throw new BusinessException(ResponseCode.ARG_NO_VERSION);
}
//当appid是ios时,version是否符合定义
if (appId.equals("IOS")) {
if (!Constants.IOS_VERSION_LIST.contains(version)) {
throw new BusinessException(ResponseCode.ARG_VERSION_VALID);
}
}
//uuid参数是否存在
String uuid = request.getParameter("uuid");
if (uuid == null) {
throw new BusinessException(ResponseCode.ARG_NO_UUID);
}
//sign校验无问题,放行
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}

在controller中调用:

    @GetMapping("/category")
@ResponseBody
public ResultUtil category(@Pattern(regexp="^[a-zA-Z]{4}$", message="分类取值错误")@RequestParam("cate") String category) {
return ResultUtil.success("this is in category");
}

3,测试效果:

错误的访问:

http://127.0.0.1:8080/home/category

正确的访问:

http://127.0.0.1:8080/home/category?appid=IOS&version=1.1&uuid=06C58F98-51F7-4C35-AC4C-B56D265CD3E9&cate=abcd

七,查看spring boot的版本

  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

spring boot:使用validator做接口的参数、表单、类中多字段的参数验证(spring boot 2.3.1)的更多相关文章

  1. Spring Security构建Rest服务-0700-SpringSecurity开发基于表单的认证

    自定义用户认证逻辑: 1,处理用户信息获取,2,用户校验,3密码的加密解密 新建:MyUserDetailService类,实现UserDetailsService接口. UserDetailsSer ...

  2. day07 ORM中常用字段和参数

    day07 ORM中常用字段和参数 今日内容 常用字段 关联字段 测试环境准备 查询关键字 查看ORM内部SQL语句 神奇的双下划线查询 多表查询前提准备 常用字段 字段类型 AutoField in ...

  3. Page_Load接收随机参数放到字典类中

    Page_Load接收随机参数放到字典类中,可以用作签名.普通的接收url的参数可以用作下面这种模式:  int appid =Convert.ToInt32(param["appid&qu ...

  4. 统一修改表单参数(表单提交的空字符串统一转null)

    统一修改表单参数(表单提交的空字符串统一转null) 1.介绍: 我们业务中有时会遇到提交的表单中某个参数为空字符串,导致后台接受的为空字符串("")而不是我们理想中的null,会 ...

  5. 使用 layUI做一些简单的表单验证

    使用 layUI做一些简单的表单验证 <form method="post" class="layui-form" > <input name ...

  6. ORM中聚合函数、分组查询、Django开启事务、ORM中常用字段及参数、数据库查询优化

    聚合函数 名称 作用 Max() 最大值 Min() 最小值 Sum() 求和 Count() 计数 Avg() 平均值 关键字: aggregate 聚合查询通常都是配合分组一起使用的 关于数据库的 ...

  7. spring boot 学习(七)小工具篇:表单重复提交

    注解 + 拦截器:解决表单重复提交 前言 学习 Spring Boot 中,我想将我在项目中添加几个我在 SpringMVC 框架中常用的工具类(主要都是涉及到 Spring AOP 部分知识).比如 ...

  8. 上手spring boot项目(一)之如何在controller类中返回到页面

    题记:在学习了springboot和thymeleaf之后,想完成一个项目练练手,于是使用springboot+mybatis和thymeleaf完成一个博客系统,在完成的过程中出现的一些问题,将这些 ...

  9. Spring MVC 文件上传、Restful、表单校验框架

    目录 文件上传 Restful Restful 简介 Rest 行为常用约定方式 Restful开发入门 表单校验框架 表单校验框架介绍 快速入门 多规则校验 嵌套校验 分组校验 综合案例 实用校验范 ...

随机推荐

  1. 记一次线上OOM问题分析与解决

    一.问题情况 最近用户反映系统响应越来越慢,而且不是偶发性的慢.根据后台日志,可以看到系统已经有oom现象. 根据jdk自带的jconsole工具,可以监视到系统处于堵塞时期.cup占满,活动线程数持 ...

  2. App测试理论简介

    一.App测试常见关注点 1.App的功能测试 功能测试都是我们首要测试的,只有功能实现了才算符合上线发布的最低标准.我们需要检测产品功能是否已实现.产品功能是否符合设计要求.产品功能是否有重复.产品 ...

  3. 不定方程(Exgcd)

    #include<cstdio> using namespace std; int x,y; inline int abs(int a){return a>?a:-a;} int e ...

  4. python中库引用与import

    在蟒蛇绘制函数中,多有turtle.   ,称它为<a>.<b>的编码风格 库引用 扩充python程序功能的方式 使用import保留字完成,采用<a>.< ...

  5. Analytics Zoo Cluster Serving自动扩展分布式推理

    作者: Jiaming Song, Dongjie Shi, Gong, Qiyuan, Lei Xia, Wei Du, Jason Dai 随着深度学习项目从实验到生产的发展,越来越多的应用需要对 ...

  6. JDK8在windows系统下安装

    一.下载 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html#JDK8 目前大部分公司内部使用的还是jdk ...

  7. 记一次Java获取本地摄像头(基于OpenCV)

    OpenCV官网下载地址(下载安装后,在安装目录可以找到动态链接库和OpenCv.jar) https://opencv.org/releases/ 安装完成后,这是我的安装目录 maven 依赖(这 ...

  8. 刷题[GXYCTF2019]禁止套娃

    梳理思路 打开网站,发现很简单,只有flag在哪里的字样. 查看源码,常用后台目录,robots.txt,都未发现有任何东西. 扫描 直接拉进扫描器一扫,发现 思考可能是git源码泄露,可能可以恢复源 ...

  9. python类中的__init__和__new__方法

    Python中类: Python中在创建类的过程中最先调用的不是__init__方法而是__new__方法,__new__方法是一个静态方法,在创建一个类对象时其实是通过__new__方法首先创建出一 ...

  10. CCNP:重发布及实验

    重发布(又:重分布.重分发):一台设备同时运行于两个协议或两个进程,默认从两端学习到的路由条目不共享:重发布技术就是人为的进行共享. 一  满足: 1.必须存在ASBR --- 自治系统边界路由器-- ...