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

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

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. 如何制作一个手机上的Github图床捷径(workflow)

    准备工作 github账号与绑定邮箱 建立一个仓库用于存放图片 生成github token 注意生成之后要备份以免后面要用到(页面刷新之后会看不见) 了解github上传文件的 GitHub API ...

  2. [Liunx]apt-get安装软件:依赖冲突问题及解决

    正常使用apt-get install安装出现依赖冲突问题: 大概是这样: ga@ubuntu:$ sudo apt-get install gcc-5-base:i386 正在读取软件包列表... ...

  3. hystrix源码小贴士之Yammer Publisher

    HystrixYammerMetricsPublisher 继承HystrixMetricsPublisher,创建HystrixYammerMetricsPublisherCommand.Hystr ...

  4. 苏大文正节点一 ORA-00603 ORA-27504 ORA-27300 ORA-27301 ORA-27302 BUG

      Problem Description --------------------------------------------------- Tue Sep 01 04:05:33 2020 s ...

  5. Jmeter(二十三) - 从入门到精通 - JMeter函数 - 上篇(详解教程)

    1.简介 在性能测试中为了真实模拟用户请求,往往我们需要让提交的表单内容每次都发生变化,这个过程叫做参数化.JMeter配置元件与前置处理器都能帮助我们进行参数化,但是都有局限性,为了帮助我们能够更好 ...

  6. HotSpot VM执行引擎的实现

    Java代码的执行分类: 第一种是将源代码编译成字节码文件,然后再运行时通过解释器将字节码文件转为机器码执行 第二种是编译执行(直接编译成机器码).现代虚拟机为了提高执行效率,会使用即时编译技术(JI ...

  7. Flutter音频播放--chewie_player的基本使用(二)——样式修改

    先贴修改图,只改了部分布局与样式 官方的demo并不十分适合我的需求,从组件进入chewie_player并没有查看到相应的布局,那么直接从chewie的依赖包进入 可以看到以下的目录结构: 我主要修 ...

  8. PJzhang:CVE-2020-1472微软NetLogon权限提升漏洞~复现

    猫宁~~~ 虚拟机上进行 安装windows 2008 R2 查看服务器ip 本地连接属性,取消ipv6,ip设置为192.168.43.158,子网掩码255.255.255.0,网关192.168 ...

  9. SCI-HUB打不开了?附SCIHUB最新下载方式

    写在前面: 今天给大家推荐一个文献下载工具包:飞鸟科研助手 www.flybird.cc输入flybird.cc同样可以访问,存书签不失联!强调下:flybird.cc 读研之前,在一家NGS生殖应用 ...

  10. netty关键字

    ------------恢复内容开始------------ buffer 本质读写的内存,三个属性:capacity.position和limit capacity:容器大小 position:读写 ...