在 SpringBoot 中使用 Spring Validation 对参数进行校验

本文将介绍在 SpringBoot 中使用 Spring Validation 进行接口参数校验的方法。首先解决了为什么要进行接口参数校验、为什么后端还需要进行参数校验、为什么要使用参数校验框架等问题。接着详细介绍了在 SpringBoot 中使用 Spring Validation 进行参数校验的方法,并列出了一些常用注解。最后,本文还解决了当前端发送的请求中有些参数不需要进行校验时应该怎么办的问题。

1. 疑问

1.1. 为什么要进行接口参数校验?

接口参数校验是指对于接口传入的参数进行检查和验证,以确保参数的合法性和正确性。这是确保系统的稳定性、安全性和可靠性的重要措施。

举例

  1. 假设我们有一个注册接口,用户需要填写用户名、密码和邮箱地址。如果我们没有进行参数校验,那么用户可以随意输入任何内容,包括非法字符和恶意代码,这可能会导致系统崩溃、数据泄露和安全问题。如果我们对参数进行校验,就可以避免这些问题,同时提高系统的可靠性和安全性。

  2. 另一个例子是支付接口,如果我们没有对金额、订单号和支付渠道进行校验,那么可能会出现支付失败、重复支付或者支付金额错误的情况。通过对参数进行校验,可以避免这些问题,同时提高支付的可靠性和安全性。

总之,接口参数校验是保证系统安全、可靠、稳定的必要措施,可以避免非法输入、恶意攻击和数据泄露等问题。

1.2. 为什么在前端已经校验了请求参数,后端的接口还需要进行参数校验呢,这样不是多此一举吗?

虽然前端已经对请求参数进行了校验,但是前端只是为了提高用户体验和减轻服务器的负担而进行的简单校验,并不能完全保证数据的安全性和正确性。因此,后端也需要对参数进行校验,以确保数据的合法性和正确性,避免恶意攻击和数据泄露等问题。

举例

  1. 假设我们有一个在线购物平台的后端接口,前端需要传入商品 ID 和购买数量,前端已经进行了简单的校验,比如购买数量必须是正整数。但是,如果一个恶意用户手动构造了一个请求,将购买数量改成负数或者 0 ,那么前端就无法阻止这个请求,这个非法请求就会到达后端,如果后端没有进行参数校验,就可能导致订单错误、库存错误或者支付异常等问题。因此,后端也需要对参数进行校验,以确保数据的合法性和正确性。

  2. 另一个例子是登录接口,前端可能会对用户名和密码进行简单的校验,比如密码长度必须大于等于 6 位。但是,如果后端没有对参数进行校验,那么可能会出现 SQL 注入、XSS 攻击等问题。比如,一个恶意用户可能会在用户名或密码中加入一些恶意代码,从而窃取用户信息或者破坏系统。通过对参数进行校验,可以避免这些问题,提高系统的安全性和可靠性。

1.3. 我们可以使用 if else 进行参数校验,为什么要使用参数校验框架呢?

虽然使用 if else 进行参数校验可以实现基本的校验功能,但是这种方式存在以下几个问题:

  1. 代码冗余:如果需要对多个接口进行参数校验,就需要在每个接口中编写相同的校验逻辑,代码冗余,维护成本高。
  2. 可读性差:如果校验逻辑比较复杂,那么使用 if else 进行校验的代码可读性较差,不利于代码的维护和优化。
  3. 安全性差:如果使用 if else 进行校验,那么恶意用户可能会利用漏洞绕过校验,从而导致安全问题。

因此,使用参数校验框架可以有效地解决上述问题,具有以下几个优点:

  1. 简化代码:使用参数校验框架可以简化校验逻辑,减少代码冗余,提高代码的可读性和可维护性。
  2. 提高安全性:参数校验框架可以避免参数注入、SQL 注入、XSS 攻击等安全问题,提高系统的安全性和可靠性。
  3. 提高效率:使用参数校验框架可以提高开发效率,减少开发时间和成本,并且可以方便地进行扩展和定制。

总之,使用参数校验框架可以有效地提高系统的安全性、可靠性和可维护性,减少代码冗余,提高开发效率,是开发过程中不可或缺的一部分。

2. 为什么要使用 Spring Validation 权限校验框架?

有哪些参数校验框架呢?

以下是几个常用的参数校验框架:

  1. Hibernate ValidatorHibernate Validator 是一个基于 Bean Validation 标准的参数校验框架,可以实现对 Java Bean 属性的校验,支持多种校验注解和自定义校验规则。
  2. Spring ValidationSpring ValidationSpring 框架提供的参数校验框架,基于 Bean Validation 标准,可以实现对 Java Bean 属性和方法参数的校验,支持多种校验注解和自定义校验规则。
  3. Apache Commons ValidatorApache Commons Validator 是一个通用的参数校验框架,支持多种校验规则和自定义校验规则,可以实现对字符串、数字、日期等数据类型的校验。
  4. JSR-303JSR-303Java EE 6 中定义的 Bean Validation 标准,提供了一套参数校验规范和 API,可以实现对 Java Bean 属性的校验,支持多种校验注解和自定义校验规则。
  5. Bean-ValidationBean-Validation 是一款轻量级的参数校验框架,基于 JSR-303 标准,可以实现对 Java Bean 属性的校验,支持多种校验注解和自定义校验规则。

这些框架都可以实现对参数的校验,提供了一套完整的校验规范和 API ,可以有效地避免非法参数和恶意攻击,提高系统的安全性和可靠性。根据不同的业务需求和技术栈,可以选择不同的框架进行参数校验。

为什么使用 Spring Validation

  1. 基于 Bean Validation 标准:Spring Validation 是基于 Bean Validation 标准的参数校验框架,可以实现对 Java Bean 属性和方法参数的校验,提供了一套完整的校验规范和 API,可以很方便地进行扩展和定制。
  2. 支持多种校验注解:Spring Validation 支持多种校验注解,比如 @NotNull、@Size、@Min、@Max 等,可以满足不同的校验需求,同时也支持自定义校验注解。
  3. 集成方便:Spring ValidationSpring 框架提供的参数校验框架,与 Spring 框架集成非常方便,可以通过简单的配置实现参数校验。
  4. 可扩展性强:Spring Validation 提供了很好的扩展性,可以自定义校验注解和校验器,满足不同的校验需求。
  5. 可读性高:Spring Validation 的校验注解非常简洁明了,代码可读性高,可以很方便地查看和维护校验逻辑。

总之,使用 pring Validation 参数校验框架可以提高代码的可读性和可维护性,减少代码冗余,提高开发效率,同时还可以有效地避免非法参数和恶意攻击,提高系统的安全性和可靠性。

3. 在 SpringBoot 中使用 Spring Validation

  1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
复制代码
  1. 定义控制器
@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    @PostMapping("/add")
    public User addUser(@RequestBody @Valid User user) {
        // ...
    }
}
复制代码

addUser 方法的参数列表中使用 @Valid 注解进行参数校验,@RequestBody 注解用于将请求体中的 JSON 对象自动映射为 Java 对象。

注意

  • 使用 @Validated 注解的时候,必须要在方法参数上添加 @Valid 注解才能生效。
  1. 定义实体类
public class User {

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

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

    @Email(message = "邮箱格式不正确")
    private String email;

    // 省略 getter 和 setter 方法
}
复制代码

User 类的属性上使用校验注解,例如 @NotBlank、@Email 等,message 属性用于指定校验失败时的错误提示信息。

  1. 定义全局异常处理器

当控制器方法的参数校验失败时,会抛出 MethodArgumentNotValidException 异常,此时可以通过定义全局异常处理器中的 handleMethodArgumentNotValidException 方法来处理该异常。

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
        StringBuilder errorMsg = new StringBuilder();
        for (ObjectError error : allErrors) {
            errorMsg.append(error.getDefaultMessage()).append("; ");
        }
        return Result.fail(errorMsg.toString());
    }

    @ExceptionHandler(Exception.class)
    public Result handleException(Exception e) {
        return Result.fail(e.getMessage());
    }
}
复制代码

GlobalExceptionHandler 类中添加对 MethodArgumentNotValidException 异常的处理,将校验失败的错误提示信息封装到 Result 对象中返回给客户端。

  1. 测试

使用 Postman 或其他工具向 /user/add 接口发送 POST 请求,请求体中需要包含 username、passwordemail 三个参数。如果参数校验失败,接口将返回错误提示信息。例如,当 username 为空时,接口将返回以下错误信息:

{
    "code": 1,
    "msg": "参数校验失败",
    "data": "用户名不能为空; "
}
复制代码

通过使用注解和异常处理器,可以非常方便地实现参数校验功能,并将校验结果返回给客户端。

4. Spring Validation 的常用注解

Spring Validation 提供了很多注解,用于对参数进行校验。

  • @NotNull:用于验证被注释的元素不为 null
@NotNull(message = "用户名不能为空")
private String username;
复制代码
  • @NotBlank:用于验证字符串必须非空。
@NotBlank(message = "用户名不能为空")
private String username;
复制代码
  • @Size:用于验证字符串、集合、数组的大小。
@Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")
private String password;

@Size(min = 1, max = 10, message = "至少选择一个爱好,最多选择10个")
private List<String> hobbies;

@Size(min = 1, max = 3, message = "至少选择一个菜,最多选择3个")
private String[] dishes;
复制代码
  • @Min@Max:用于验证数字的最小值和最大值。
@Min(value = 1, message = "年龄必须大于等于1岁")
@Max(value = 150, message = "年龄必须小于等于150岁")
private Integer age;
复制代码
  • @DecimalMin@DecimalMax:用于验证浮点数或 igDecimal 的最小值和最大值。
@DecimalMin(value = "0.01", message = "价格不能低于0.01元")
@DecimalMax(value = "9999.99", message = "价格不能超过9999.99元")
private BigDecimal price;
复制代码
  • @Pattern:用于验证字符串是否符合正则表达式。
@Pattern(regexp = "^[a-zA-Z0-9]{4,20}$", message = "用户名必须由4-20个字母或数字组成")
private String username;
复制代码
  • @Email:用于验证字符串是否为合法的电子邮件地址。
@Email(message = "请输入正确的邮箱地址")
private String email;
复制代码
  • @Valid:用于标记需要递归校验的对象。
public class User {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Valid
    private Address address;

    // getter 和 setter 方法省略
}

public class Address {
    @NotBlank(message = "所在城市不能为空")
    private String city;

    @NotBlank(message = "详细地址不能为空")
    private String detail;

    // getter 和 setter 方法省略
}
复制代码

这些注解可以也用于控制器的方法参数校验。

@RestController
@Validated
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
        // 处理请求,创建用户
        return ResponseEntity.ok(user);
    }
}
复制代码

在上面的示例中,@Valid 注解用于标记 User 对象需要进行校验。在方法执行时,Spring 会自动根据 User 对象的属性上的校验注解对其进行校验,如果校验不通过,则会抛出 ConstraintViolationException 异常。

需要注意的是,为了使校验注解生效,控制器类需要添加 @Validated 注解。

5. 前端发送的请求,有些参数不需要进行校验怎么办?

前端发送的请求,有些参数不需要进行校验。比如我们更新一个用户,而我们对 JavaBean 的所有参数都进行了校验,那这个请求就被拒绝了,这种请求我们应该怎么办?

当我们在校验接口参数时,有些 JavaBean 参数不需要进行校验,可以使用 Spring Validation 提供的 @Validated 和 @Valid 注解结合分组校验来实现。

首先,在 JavaBean 参数上使用校验注解时,可以为注解指定一个分组。示例代码如下:

public class User {

    @NotBlank(message = "用户名不能为空", groups = {Create.class, Update.class})
    private String username;

    @NotBlank(message = "密码不能为空", groups = {Create.class})
    private String password;

    @NotBlank(message = "手机号不能为空", groups = {Create.class})
    private String mobile;

    // getter 和 setter 方法省略
}
复制代码

在上面的示例中,@NotBlank 注解被分为了两个组:CreateUpdate,对应着创建和更新操作。这样,当我们对 User 对象进行校验时,只需要指定需要校验的组即可。示例代码如下:

@RestController
@Validated
public class UserController {

    @PostMapping("/users")
    public ResponseEntity<User> createUser(@Validated(Create.class) @RequestBody User user) {
        // 处理请求,创建用户
        return ResponseEntity.ok(user);
    }

    @PutMapping("/users/{id}")
    public ResponseEntity<User> updateUser(@PathVariable Long id, @Validated(Update.class) @RequestBody User user) {
        // 处理请求,更新用户
        return ResponseEntity.ok(user);
    }
}
复制代码

在上面的示例中,@Validated 注解用于标记控制器类需要进行校验,并通过 groups 属性指定需要校验的分组。@Validated 注解可以作用在控制器类或方法上。@Validated 注解和 @Valid 注解的区别在于,@Validated 注解可以指定需要校验的分组,而 @Valid 注解不支持分组校验。

这样,当我们在创建用户时,只会校验 username、password 和 mobile 参数,而在更新用户时,只会校验 username 参数,不会对 passwordmobile 参数进行校验。

猜你喜欢

转载自juejin.im/post/7217267332657446972