API安全(五)-参数校验

1、为什么要做数据校验

  要保证系统的安全性,健壮性,数据校验必不可少,校验参数的合法性,不能因为前端或者其它调用段因为参数传的不对导致我们的系统报错。

2、开发中参数校验做在哪里

  一般都是做在接口层面,对传入的参数进行校验。

3、Bean Validation

  对于Controller接口的参数校验,如果参数较少可以自己写代码进行校,但是如果参数较多,就会由一堆if-else,代码不美观。我们可以使用Bean Validation来进行参数校验,Bean Validation是一种Java规范,可以允许使用注释来对对象上的字段进行约束,也可以自定义约束。从1.0(JSR303),1.1(JSR349)发展到现在的2.0(JSR380)。使用Bean Validation 2.0需要Java 8或更高版本,由Hibernate Validator作为提供商。

4、在SpringBoot中使用Bean Validation

  如果我们使用了spring-boot-starter-web依赖,那么就会自动为我们将相关依赖添加进来。如果想单独使用的话,需要添加spring-boot-starter-validation依赖。

5、Bean Validation中的校验注解

validation-api中在javax.validation.constraints包下提供了一系列标准校验注解

  @AssertFalse:值必须是false。支持的类型有boolean和Boolean。null值被认为是合法的。

  @AssertTrue:值必须是true。支持的类型有boolean和Boolean。null值被认为是合法的。

  @DecimalMax:值必须小于等于value指定的值;是否包含value由inclusive控制,默认为包含(true)。支持的类型有BigDecimal、BigInteger、字符串、byte、short、int、long及其包装类。不支持double和float。null值被认为是合法的。

  @DecimalMin:值必须大于等于value指定的值;是否包含value由inclusive控制,默认为包含(true)。支持的类型有BigDecimal、BigInteger、字符串、byte、short、int、long及其包装类。不支持double和float。null值被认为是合法的。

  @Digits:必须是指定范围内的数字。integer指定整数部分最大长度;fraction指定小数部分最大长度。支持的类型有BigDecimal、BigInteger、字符串、byte、short、int、long及其包装类。null值被认为是合法的。

  @Email:字符串必须是格式正确的电子邮件地址。也可以通过regexp和flag指定自定义的email格式。null值被认为是合法的。

  @Future:必须是未来的时间或日期。支持的类型有Date、Instant、LocalDate、LocalDateTime、LocalTime等常用时间日期类。null值被认为是合法的。

  @FutureOrPresent:必须是当前或未来的时间或日期。支持的类型有Date、Instant、LocalDate、LocalDateTime、LocalTime等常用时间日期类。null值被认为是合法的。

  @Max:值必须小于等于指定的value。支持的类型有BigDecimal、BigInteger、byte、short、int、long及其包装类。不支持double和float。null值被认为是合法的。

  @Min:值必须大于等于指定的value。支持的类型有BigDecimal、BigInteger、byte、short、int、long及其包装类。不支持double和float。null值被认为是合法的。

  @Negative:值必须是负数(小于零)。支持的类型有BigDecimal、BigInteger、byte、short、int、long、float、double及其包装类。null值被认为是合法的。

  @NegativeOrZero:值必须小于等于零。支持的类型有BigDecimal、BigInteger、byte、short、int、long、float、double及其包装类。null值被认为是合法的。

  @NotBlank:字符串不能为空,并且必须至少包含一个非空白字符。null值不合法。

  @NotEmpty:值不能为空。支持的类型,字符串不能为null且长度不能为0;集合不能为null且不能为空;Map不能为null且不能为空;数组不能为null且不能为空。

  @NotNull:值不能为null。支持任何类型。

  @Null:值必须是null。支持任何类型。

  @Past:必须是过去的时间或日期。支持的类型有Date、Instant、LocalDate、LocalDateTime、LocalTime等常用时间日期类。null值被认为是合法的。

  @PastOrPresent:必须是当前或过去的时间或日期。支持的类型有Date、Instant、LocalDate、LocalDateTime、LocalTime等常用时间日期类。null值被认为是合法的。

  @Pattern:必须与指定的正则表达式匹配。null值被认为是合法的。

  @Positive:值必须是正数(大于零)。支持的类型有BigDecimal、BigInteger、byte、short、int、long、float、double及其包装类。null值被认为是合法的。

  @Size:个数必须在指定的min和max之间(包括min和max)。支持的类型字符串长度,集合中元素个数,Map中键值对个数,数组的长度。null值被认为是合法的。

hibernate-validator中在org.hibernate.validator.constraints包下也提供了一系列校验注解,常用的如下:

  @Length:字符串长度在指定的min和max之间(包括min和max)。null值被认为是合法的。

  @Range:值必须在适当的范围min和max之间(包括min和max)。应用于数值或数值的字符串表示形式。null值被认为是合法的。 @URL:字符串是否为URL。也可以通过regexp和flag指定自定义的email格式。null值被认为是合法的。

6、添加用户使用参数校验

  6.1、UserDTO上添加校验注解

/**
 * @author caofanqi
 * @date 2020/1/20 13:08
 */
@Data
public class UserDTO {

    @Null(message = "创建用户时,id必须为null")
    private Long id;

    @NotNull(message = "名称不能为空")
    private String name;

    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;

}

  6.2、在接口类上添加@Validated注解

@Validated
@RestController
@RequestMapping("/users")
public class UserController {......}

  6.3、在接口上添加@Validated或@Valid注解

    @PostMapping
    public UserDTO create(@RequestBody @Validated UserDTO userDTO){
        return userService.create(userDTO);
    }

  6.4、测试添加用户,这这时就会对我们的参数进行校验,如果不符合我们的要求就会报错

  正确的参数

7、异常结果处理1

  在错误参数访问时,通过控制台打印可以知道会抛出一个org.springframework.web.bind.MethodArgumentNotValidException异常,

  我们对其进行捕获并处理,使返回结果更友好。

/**
 * 异常处理器
 *
 * @author caofanqi
 * @date 2020/1/27 21:17
 */
@RestControllerAdvice
public class ControllerAdvice {

    /**
     * @param e 参数校验异常
     * @return 具体不符合规范的参数及其原因
     */
    @ExceptionHandler
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public Map<String, String> exceptionHandler(org.springframework.web.bind.MethodArgumentNotValidException e) {
        return e.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
    }

}

  再次测试错误参数情况,返回的结果就比较友好了

8、分组校验

  对于添加用户,我们是不需要用户id的,但是修改用户是需要用户id的。这时怎么进行校验呢?Bean Validation为我们提供了分组功能,其实每一个校验注解都有一个groups属性,且默认为空(Default分组)。

  8.1、新建两个接口,Create、Update并继承Default接口。(不继承的话,不会校验默认分组)

/**
 *  新增分组
 * @author caofanqi
 * @date 2020/1/27 21:39
 */
public interface Create extends Default {
}

/**
 * 修改分组
 * @author caofanqi
 * @date 2020/1/27 21:40
 */
public interface Update extends Default {
}

  8.2、修改UserDTO上id的注解,为其添加分组

    @Null(groups = Create.class,message = "创建用户时,id必须为null")
    @NotNull(groups = Update.class,message = "修改用户时,必须有id")
    private Long id;

  8.3、在接口的@Validated上添加分组

    @PostMapping
    public UserDTO create(@RequestBody @Validated(Create.class) UserDTO userDTO){
        return userService.create(userDTO);
    }

    @PutMapping
    public UserDTO update(@RequestBody @Validated(Update.class) UserDTO userDTO) {
        return userService.update(userDTO);
    }

  8.4、测试错误参数结果如下

    

9、级联校验(对象属性中的属性校验)

  对于属性是对象的,如果我们只添加了@NotNull注解,它也不会去校验该对象中的属性,如果想要进行校验,需要添加@Valid注解。

  9.1、强行举例,将手机号放到详情对象中

/**
 * 用户详情,为了测试级联校验
 *
 * @author caofanqi
 * @date 2020/1/27 22:39
 */
@Data
public class UserInfoDTO {

    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp="^[1]([3-9])[0-9]{9}$",message="手机号格式不正确")
    private String phone;

}

  9.2、在UserDTO中想要校验电话号添加@Valid注解

    @Valid
    @NotNull(message = "用户详情不能为空")
    private UserInfoDTO userInfo;

  9.3、测试如下

10、自定义注解校验

  如果给我们提供的校验注解不能满足我们的需求时,我们可以自定义校验注解。

  10.1、创建一个注解,找一个原有校验注解进行copy,修改@Constraint指定校验逻辑(可以指定多个),和所需要的属性即可。

/**
 * 自定义校验注解,具体的校验逻辑由@Constraint进行指定,可以指定多个
 *
 * @author caofanqi
 */
@Documented
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = {CustomConstraintValidatorForString.class, CustomConstraintValidatorForList.class})
public @interface CustomConstraint {

    String message() default "自定义校验注解";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    int value() default 0;

}

  10.2、创建校验器,实现ConstraintValidator接口,并指定要校验的注解和要校验的类型,initialize可以获得注解上的属性,进行初始化;isValid校验逻辑。

/**
 * 自定义注解校验器,实现ConstraintValidator接口,并指定要校验的注解和要校验的类型,
 * initialize可以获得注解上的属性,进行初始化;isValid校验逻辑。
 *
 * @author caofanqi
 * @version 1.0.0
 * @date 2020/1/27 23:17
 */
@Slf4j
public class CustomConstraintValidatorForString implements ConstraintValidator<CustomConstraint,String> {

    private int value;

    @Override
    public void initialize(CustomConstraint constraintAnnotation) {
        value = constraintAnnotation.value();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        log.info("自定义校验的参数值是:{},注解上的值是:{}",value,this.value);
        return true;
    }

}

  10.3、测试,控制台打印如下,说明执行了校验逻辑

11、list中做分组校验

  如果有批量添加功能,需要对整个list进行校验那么如下

  11.1、将@Validated(Create.class)注解添加在方法上

  11.2、在List中添加@Valid注解

    @PostMapping("/batch")
    @Validated(Create.class)
    public void batchCreate(@RequestBody  List<@Valid UserDTO> userDTOS){
        userService.batchCreate(userDTOS);
    }

  11.3、测试错误参数,有报错,结果不够友好

12、异常结果处理2

  错误参数访问时,通过控制台打印可以知道会抛出一个javax.validation.ConstraintViolationException异常,我们对其进行捕获并处理,使返回结果更友好。

    /**
     * @param e 参数校验异常2
     * @return 具体不符合规范的路径及其原因
     */
    @ExceptionHandler
    @ResponseStatus(code = HttpStatus.BAD_REQUEST)
    public Map<Path, String> exceptionHandler(javax.validation.ConstraintViolationException e){
        return e.getConstraintViolations().stream()
                .collect(Collectors.toMap(ConstraintViolation::getPropertyPath, ConstraintViolation::getMessage));
    }

  再次测试错误参数情况,返回的结果就比较友好了

13、属性之间的关联校验

  这里我们可以自定义一个类校验注解,这样就可以拿到所有的属性,然后进行校验。比如说name不能和username相同。

  13.1、自定义类注解

/**
 * 自定义类级别注解
 * @author caofanqi
 */
@Documented
@Target({TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = {UserDTOConstraintValidator.class})
public @interface UserDTOConstraint {

    String message() default "";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

  13.2、校验器

/**
 * 自定义UserDTO类校验器
 *
 * @author caofanqi
 * @date 2020/1/28 1:36
 */
@Slf4j
public class UserDTOConstraintValidator implements ConstraintValidator<UserDTOConstraint, UserDTO> {

    @Override
    public boolean isValid(UserDTO value, ConstraintValidatorContext context) {

        if (value.getName().equals(value.getUsername())){
            /*
             * 更改错误消息
             */
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("名称不能和用户名相同")
                    .addPropertyNode("name").addConstraintViolation();

            return false;
        }

        return true;
    }
}

  13.3、在类上添加该注解

@Data
@UserDTOConstraint(message = "default message")
public class UserDTO {
......
}

  13.4、测试

Bean Validation官网:https://beanvalidation.org/

Hibernate Validator官方参考文档:https://docs.jboss.org/hibernate/stable/validator/reference/en-US/html_single/#preface

项目源码:https://github.com/caofanqi/study-security/tree/dev-bean-validation

原文地址:https://www.cnblogs.com/caofanqi/p/12237348.html

时间: 2024-08-30 14:09:38

API安全(五)-参数校验的相关文章

Spring Boot2 系列教程 (十五) | 服务端参数校验之一

估计很多朋友都认为参数校验是客户端的职责,不关服务端的事.其实这是错误的,学过 Web 安全的都知道,客户端的验证只是第一道关卡.它的参数验证并不是安全的,一旦被有心人抓到可乘之机,他就可以有各种方法来摸拟系统的 Http 请求,访问数据库的关键数据.轻则导致服务器宕机,重则泄露数据.所以,这时就需要设置第二道关卡,服务端验证了. 老项目的服务端校验 @RestController @RequestMapping("/student") public class ValidateOne

springmvc参数校验+统一异常处理

一.PathVariable 校验 在定义 Restful 风格的接口时,通常会采用 PathVariable 指定关键业务参数,如下: @GetMapping("/path/{group:[a-zA-Z0-9_]+}/{userid}") @ResponseBody public String path(@PathVariable("group") String group, @PathVariable("userid") Integer us

jQuery form插件的使用--用 formData 参数校验表单,验证后提交(简单验证).

Form Plugin API 里提供了很多有用的方法可以让你轻松的处理表单里的数据和表单的提交过程. 测试环境:部署到Tomcat中的web项目. 一.引入依赖js <script src="jquery-1.3.1.js" type="text/javascript"></script> <script src="jquery.form.js" type="text/javascript"&g

Spring 进入Controller前参数校验

在进入Controller前完成参数的校验,针对对象参数 分为两个验证方式 (1)直接使用已定义的校验方式 1.在需要进行校验的属性上增加校验类型注解 import java.util.Date; import javax.validation.constraints.Past; import org.hibernate.validator.constraints.NotBlank; public class User { private String id; private String us

API接口防止参数篡改和重放攻击

{近期领导要求我对公司业务的支付类的ocr接口做研究,是否存在支付接口重放攻击,so.....} API重放攻击(Replay Attacks)又称重播攻击.回放攻击.他的原理就是把之前窃听到的数据原封不动的重新发送给接收方.HTTPS并不能防止这种攻击,虽然传输的数据是经过加密的,窃听者无法得到数据的准确定义,但是可以从请求的接收方地址分析出这些数据的作用.比如用户登录请求时攻击者虽然无法窃听密码,但是却可以截取加密后的口令然后将其重放,从而利用这种方式进行有效的攻击. 所谓重放攻击就是攻击者

易元平台-参数校验

前台校验: 1.设置校验内容,如设置为Email 后台校验: 2.参数校验 业务对象的参数列表 - 设置参数校验 修改服务中的校验规则(部分校验返回|全部校验返回) 设置按钮的控制器为serviceUf类型 js校验

【高德地图API】从零开始学高德JS API(五)路线规划——驾车|公交|步行

先来看两个问题:路线规划与导航有什么区别?步行导航与驾车导航有什么区别? 回答: 1.路线规划,指的是为用户提供3条路线推荐.[高德]在提供路线规划的时候,会提供用户自定义路线规划功能,这是别家没有做到的.导航,指的是为驾车用户提示路口信息,向左向右,进入匝道等信息. 2.我们这里说的步行导航和驾车导航,严格的说,应该是路线规划.从A地到B地,如果是驾车,路线规划会将公路路网做为搜索数据:如果是步行,过街天桥.地下通道.人行道做为搜索数据. ---------------------------

为List&lt;T&gt;中的T进行参数校验

1.现在前端发送了一个POST请求,他的Data是一个数组,而不是对象(jsonObj的值两侧是中括号). var jsonObj = [{'id':11, 'name':'叵'}, {'id':12, 'name':'卜'}]; $.ajax({ type : "post", // 省略 data : JSON.stringify(jsonObj), // 省略 }); 2.后端的请求方法应该让一个Input的数组对象,或是Input泛型的List对象入参,来进行数据绑定 @Requ

一个简单的参数校验类

当我们写js的重载函数的时候,我们需要判断不同的输入情况. 而,一般我们是根据参数的个数来判断的,但是,常常情况并非如此: function abc(a,b,c) a:string b:number c:boolean 或者 a:string b:string c:number 这两种情况都是参数个数为3,但是参数类型却完全不同.如何的方便的构建一个重载函数呢? 用我的类即可: 类代码(很简短): var Param = { varify: function (oParam, sParamTyp