对于任何一个应用而言,客户端做的数据有效性验证都不是安全有效的,而数据验证又是一个企业级项目架构上最为基础的功能模块,这时候就要求我们在服务端接收到数据的时候也对数据的有效性进行验证。为什么这么说呢?往往我们在编写程序的时候都会感觉后台的验证无关紧要,毕竟客户端已经做过验证了,后端没必要在浪费资源对数据进行验证了,但恰恰是这种思维最为容易被别人钻空子。毕竟只要有点开发经验的都知道,我们完全可以模拟
HTTP
请求到后台地址,模拟请求过程中发送一些涉及系统安全的数据到后台,后果可想而知
文章目录
JSR-303 注释介绍
这里只列举了javax.validation
包下的注解,包含了hibernate-validator
验证包中的常用注解
注解 | 说明 |
---|---|
@NotNull |
限制必须不为null |
@NotEmpty |
验证注解的元素值不为 null 且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank |
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Pattern(value) |
限制必须符合指定的正则表达式 |
@Size(max,min) |
限制字符长度必须在 min 到 max 之间(也可以用在集合上) |
@Email |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 |
@Max(value) |
限制必须为一个不大于指定值的数字 |
@Min(value) |
限制必须为一个不小于指定值的数字 |
@DecimalMax(value) |
限制必须为一个不大于指定值的数字 |
@DecimalMin(value) |
限制必须为一个不小于指定值的数字 |
@Null |
限制只能为null(很少用) |
@AssertFalse |
限制必须为false (很少用) |
@AssertTrue |
限制必须为true (很少用) |
@Past |
限制必须是一个过去的日期 |
@Future |
限制必须是一个将来的日期 |
@Digits(integer,fraction) |
限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction (很少用) |
Hibernate Validator拓展的注解
注解 | 说明 |
---|---|
校验邮件地址 | |
@CreditCardNumber | 校验信用卡号码 |
@Length(min=, max=) | 功能同@Size,但是只支持String类型 |
@URL | 字符串是否合法的URL |
@Range(min=,max=,message=) | 被注释的元素必须在合适的范围内 |
hibernate validator校验demo
实体类
@Getter
@Setter
@NoArgsConstructor
public class DemoModel {
@NotBlank(message="用户名不能为空")
private String userName;
@NotBlank(message="年龄不能为空")
@Pattern(regexp="^[0-9]{1,2}$",message="年龄不正确")
private String age;
@AssertFalse(message = "必须为false")
private Boolean isFalse;
/**
* 如果是空,则不校验,如果不为空,则校验
*/
@Pattern(regexp="^[0-9]{4}-[0-9]{2}-[0-9]{2}$",message="出生日期格式不正确")
private String birthday;
}
hibernate的校验模式
1、普通模式(默认是这个模式)
普通模式(会校验完所有的属性,然后返回所有的验证失败信息)
2、快速失败返回模式
快速失败返回模式(只要有一个验证失败,则返回)
两种验证模式配置方式:(参考官方文档)
failFast:true 快速失败返回模式 false 普通模式
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .failFast( true ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator();
和 (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式)
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) .configure() .addProperty( "hibernate.validator.fail_fast", "true" ) .buildValidatorFactory(); Validator validator = validatorFactory.getValidator();
hibernate的校验实现
@Configuration
public class ValidatorConfiguration {
@Bean
public Validator validator(){
ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
.configure()
.addProperty( "hibernate.validator.fail_fast", "true" )
.buildValidatorFactory();
Validator validator = validatorFactory.getValidator();
return validator;
}
}
1、请求参数校验 @Valid
在@RequestBody DemoModel demo之间加注解 @Valid,然后后面加BindindResult即可; BindingResult是验证不通过的结果集合 。
@RestController
public class VaildController {
@RequestMapping("/vaild")
public String vaild(@RequestBody(required = false) @Valid VaildModel model,
BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
System.out.println(JSON.toJSONString(result.getAllErrors()));
return "success";
}
}
请求参数
POST请求传入的参数:{"userName":"dd","age":120,"isFalse":true,"birthday":"21010-21-12"}
2、GET参数校验(@RequestParam参数校验)
使用@Valid注解,对RequestParam对应的参数进行注解,是
无效
的,需要使用@Validated注解来使得验证生效。
方法所在的Controller上加注解@Validated
@RestController
@Validated
public class ValidationController {
/**如果只有少数对象,直接把参数写到Controller层,然后在Controller层进行验证就可以了。*/
@RequestMapping(value = "/vaild2", method = RequestMethod.GET)
public String vaild2(@Range(min = 1, max = 9, message = "年级只能从1-9")
@RequestParam(name = "grade", required = true)
int grade,
@Min(value = 1, message = "班级最小只能1")
@Max(value = 99, message = "班级最大只能99")
@RequestParam(name = "classroom", required = true)
int classroom) {
System.out.println(grade + "," + classroom);
return grade + "," + classroom;
}
/**
* 验证不通过时,抛出了ConstraintViolationException异常,使用同一捕获异常处理
*/
@ControllerAdvice
@Component
class GlobalExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public String handle(ValidationException exception) {
List<String> errorMsg = new ArrayList<>();
if(exception instanceof ConstraintViolationException){
ConstraintViolationException exs = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> violations = exs.getConstraintViolations();
for (ConstraintViolation<?> item : violations) {
/**打印验证不通过的信息*/
System.out.println(item.getMessage());
errorMsg.add(item.getMessage());
}
}
return "bad request, " +JSON.toJSONString(errorMsg);
}
}
}
3、model校验
@Autowired
private Validator validator;
@RequestMapping("/vaild3")
public String vaild3(@RequestBody(required = false) VaildModel vaildModel){
Set<ConstraintViolation<VaildModel>> violationSet = validator.validate(vaildModel);
for (ConstraintViolation<VaildModel> model : violationSet) {
System.out.println(model.getMessage());
}
return "success";
}
4、对象级联校验
对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证:(验证Demo2示例时,可以验证Demo2的字段)
@Data
public class Demo2 {
@Size(min = 3,max = 5,message = "list的Size在[3,5]")
private List<String> list;
@NotNull
@Valid
private Demo3 demo3;
}
@Data
public class Demo3 {
@Length(min = 5, max = 17, message = "length长度在[5,17]之间")
private String extField;
}
5、分组校验
有这样一种场景,新增用户信息的时候,不需要验证userId(因为系统生成);
修改的时候需要验证userId,这时候可用用户到validator的分组验证功能
创建分组
public interface GroupA {
}
public interface GroupB {
}
@Data
public class Person {
@NotBlank(message = "不能为空",groups = {GroupA.class})
/**用户id*/
private Integer userId;
@Length(min = 4,max = 20,message = "必须在[4,20]",groups = {GroupB.class})
@Length(min = 1,max = 3,message = "必须在[1,3]",groups = {GroupA.class})
/**用户名*/
private String userName;
}
@RequestMapping("/vaild4")
public String vaild4(@RequestBody @Validated({GroupA.class,Default.class}) Person person,
BindingResult result){
if(result.hasErrors()){
for (ObjectError error : result.getAllErrors()) {
System.out.println(error.getDefaultMessage());
}
}
System.out.println(JSON.toJSONString(result.getAllErrors()));
return "success";
}
自定义验证器
一般情况,自定义验证可以解决很多问题。但也有无法满足情况的时候,此时,我们可以实现validator的接口,自定义自己需要的验证器。
实现了一个自定义的大小写验证器
public enum CaseMode {
UPPER,
LOWER;
}
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckCaseValidator.class)
@Documented
public @interface CheckCase {
String message() default "";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
CaseMode value();
}
public class CheckCaseValidator implements ConstraintValidator<CheckCase, String> {
private CaseMode caseMode;
public void initialize(CheckCase checkCase) {
this.caseMode = checkCase.value();
}
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (s == null) {
return true;
}
if (caseMode == CaseMode.UPPER) {
return s.equals(s.toUpperCase());
} else {
return s.equals(s.toLowerCase());
}
}
}