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

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

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. python中使用cookie进行模拟登录

    背景:使用cookie模拟登录豆瓣->我的豆瓣网页 [准备工作] 1.通过Fiddler抓取“我的豆瓣”url: 2.通过Fiddler抓取“我的豆瓣”cookie值. import urlli ...

  2. PHP实现Restful风格的API(转)

    Restful是一种设计风格而不是标准,比如一个接口原本是这样的: http://www1.qixoo.com/user/view/id/1表示获取id为1的用户信息,如果使用Restful风格,可以 ...

  3. tcp、http 学习小结

    tcp.http 学习小结 前言 最近因为cdn的一个问题,困扰了自己好久.因为需要统计网站访问的成功数,而且要求比较精确.目前的实现不能满足要求,因为没有区别访问成功与否,也没有对超时做处理.期间解 ...

  4. CentOS 7使用PuppeteerSharp无头浏览器注意事项

    环境: CentOS 7.6.1810 .net core 3.1 PuppeteerSharp 2.0.0 1.如网络部稳定可以提前下载需要的chromium 下载地址:https://storag ...

  5. 为什么安装了MinGW之后,还是不能在Matlab中使用mex?

    原文地址:http://blog.sina.com.cn/s/blog_53c7b1580102xjcw.html 老版本的Matlab自带lcc,在Matlab中输入mex -setup就可以选择. ...

  6. 利用Node实现HTML5离线存储

    前言 支持离线Web应用开发是HTML5的一个重点.离线Web应用就是在设备不能上网的时候仍然可以运行的应用.开发离线Web应用需要几个步骤,其中一个就是离线下必须能访问一定的资源(图像 JS css ...

  7. 栈帧的内部结构--操作数栈(Opreand Stack)

    每个栈帧中包含: 局部变量表(Local Variables) 操作数栈(Opreand Stack) 或表达式栈 动态链接 (Dynamic Linking) (或指向运行时常量的方法引用) 动态返 ...

  8. java安全编码指南之:可见性和原子性

    目录 简介 不可变对象的可见性 保证共享变量的复合操作的原子性 保证多个Atomic原子类操作的原子性 保证方法调用链的原子性 读写64bits的值 简介 java类中会定义很多变量,有类变量也有实例 ...

  9. 从SpringBoot源码看资源映射原理

    前言 很多的小伙伴刚刚接触SpringBoot的时候,可能会遇到加载不到静态资源的情况. 比如html没有样式,图片无法加载等等. 今天王子就与大家一起看看SpringBoot中关于资源映射部分的主要 ...

  10. burp suite 之 Scanner(漏洞扫描)

    Scanner选项:是一个进行自动发现 web 应用程序的安全漏洞的工具. 将抓取的包 通过选项卡发送至 Scanner下的Scan queue 首先来介绍 Scanner 下的 lssue acti ...