确保数据安全!使用Spring Boot 实现强大的API参数验证

科技   2024-11-01 09:06   中国香港  

我们在项目开发中,出于对数据完整性的考虑,基本上每个接口都需要参数校验,参数校验可以自己手动校验,也可以用工具校验,今天松哥和大家分享如何利用 Spring Boot 自带的工具实现参数校验。

一 前端 or 后端?

参数校验应该在前端完成还是后端完成?

正常来说,前后端都是需要校验的,但是前后端校验的目的各不相同。

一般来说,前端校验可以满足两个需求:

  1. 用户体验:前端校验可以即时反馈给用户,减少等待服务器响应的时间,提高用户体验。
  2. 减轻服务器负担:通过前端校验可以过滤掉一些明显无效的请求,减少不必要的服务器负载。

真正要确保数据完整性,还得要靠后端,后端校验可以起到如下作用:

  1. 安全性:由于前端代码可以被绕过或修改。后端校验是安全的必要保障,确保即使前端校验被绕过,数据的安全性和完整性也能得到保证。
  2. 数据一致性:后端校验可以确保所有通过的请求都符合业务逻辑和数据模型的要求,保持数据的一致性。
  3. 容错性:后端校验可以处理那些前端未能覆盖到的异常情况,作为最后一道防线。
  4. 跨平台一致性:后端校验确保了无论用户通过何种客户端(Web、移动应用、第三方 API 等)访问服务,数据校验的标准都是一致的。
  5. 维护和可扩展性:后端校验逻辑通常更容易维护和更新,因为它们集中在服务器端,而不是分散在多个客户端。
  6. 日志和监控:后端可以记录校验失败的请求,这对于监控系统安全和进行问题诊断非常有用。

因此,后端校验才能真正确保数据的完整性,今天松哥也是要和大家聊一聊后端数据校验。

二 参数校验注解

2.1 参数校验依据

在 Spring Boot 中,数据校验是通过 JSR303/JSR380 规范的 Bean Validation 实现的。

这里涉及到两个概念,松哥和大家简单说下。

JSR303 是 Bean Validation 的 1.0 版本,正式名称为《Bean Validation》。它提供了一套注解和 API 来定义 Java 对象(Bean)的验证规则。这些注解可以直接用于 Bean 的属性上,以声明式的方式定义验证逻辑。JSR303 定义了一组标准的验证注解,如 @NotNull@Size@Email 等,用于校验对象的属性是否满足特定的条件。

而 JSR380 则是 Bean Validation 的 2.0 版本,也称为《Jakarta Bean Validation 2.0》。随着 JavaEE 向 JakartaEE 的迁移,JSR380 成为了新的规范。JSR380 在 JSR303 的基础上进行了扩展和改进,增加了新的注解、改进了 API,并提供了更好的集成方式。JSR380 的注解与 JSR303 兼容,但增加了一些新的注解,如 @Emailmessage 属性支持国际化,以及 @PositiveOrZero@NegativeOrZero 等。

松哥下面案例主要和小伙伴们分享最新的 JSR380 规范中的参数校验注解。

2.2 代码实践

现在我们创建一个 Spring Boot 项目,使用当前最新版,并且引入参数校验依赖,最终创建好的工程依赖如下:

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}

假设我现在有一个 UserDto 类,需要进行参数校验,那么我可以按照如下方式定义 UserDto:

/**
 * @author:江南一点雨
 * @site:http://www.javaboy.org
 * @微信公众号:江南一点雨
 * @github:https://github.com/lenve
 * @gitee:https://gitee.com/lenve
 */

public class UserDto {
    @NotNull(message = "用户名不能为空")
    private String username;
    @NotBlank(message = "密码不能为空")
    private String password;
    @NotEmpty(message = "邮箱不能为空")
    private String email;
    //省略 getter/setter


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

接下来在 Controller 的方法参数前使用 @Validated 注解来开启校验。

/**
 * @author:江南一点雨
 * @site:http://www.javaboy.org
 * @微信公众号:江南一点雨
 * @github:https://github.com/lenve
 * @gitee:https://gitee.com/lenve
 */

@RestController
public class UserController {
    @GetMapping("/hello")
    public String hello(@Validated UserDto userDto, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 处理校验失败情况
        }
        return "200";
    }
}

当参数校验失败时,会抛出 MethodArgumentNotValidException 异常。可以在全局异常处理器中捕获该异常并进行统一处理。

/**
 * @author:江南一点雨
 * @site:http://www.javaboy.org
 * @微信公众号:江南一点雨
 * @github:https://github.com/lenve
 * @gitee:https://gitee.com/lenve
 */

@RestControllerAdvice
public class GlobalException {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String handleValidationExceptions(MethodArgumentNotValidException ex
{
        // 获取校验结果的错误信息
        String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        return message;
    }
}

如此就大功告成了~是不是非常 Easy?

2.3 异常提示优化

上面参数校验注解中的异常提示都是在 Java 代码里边硬编码的,我们也可以提前定义好异常提示文本,然后在代码里引用即可,这样更加方便,也好维护。

在 Spring Boot 项目中,可以通过在 messages.properties 文件中定义异常提示文本,并在代码中通过 @Message 注解引用这些文本来实现国际化和自定义错误消息。

具体步骤是这样的:

  1. 创建 messages.properties 文件:在 src/main/resources 目录下创建一个 messages.properties 文件(对于不同语言版本,可以创建如 messages_en.propertiesmessages_fr.properties 等文件)。
  2. 定义异常提示文本:在 messages.properties 文件中定义键值对,键用于在代码中引用,值是实际的错误消息。
NotEmpty.username=用户名不能为空
NotBlank.password=密码不能为空
Email.email=邮箱格式不正确
  1. 在实体类或 DTO 上使用校验注解
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import org.hibernate.validator.constraints.NotEmpty;

public class UserDto {
    @NotNull(message = "{NotEmpty.username}")
    private String username;
    
    @NotBlank(message = "{NotBlank.password}")
    private String password;
    
    @Email(message = "{Email.email}")
    private String email;

    // Getters and setters
}
  1. 配置国际化:如果你的应用需要支持多语言,可以在 application.propertiesapplication.yml 中配置消息源。
spring.messages.basename=messages
spring.messages.encoding=UTF-8

这样,当校验失败时,Spring 将自动从 messages.properties 文件中查找对应的错误消息,并将其返回给客户端。这种方法不仅可以使错误消息更加灵活和可维护,还可以方便地实现国际化。

三 什么是分组校验

为什么需要分组校验呢?

假设我们有一个用户实体 User,它包含用户名、密码和邮箱三个字段。在用户注册时,我们需要校验用户名和密码非空,邮箱格式正确。但在用户信息更新时,我们只需要校验用户名和邮箱,密码可能不会被修改,因此不需要校验。对于这种需求,我们可以使用分组校验来实现这一需求。

松哥通过一个具体的案例来和小伙伴们演示下。

首先,我们定义两个校验分组,一个用于注册,一个用于更新:

public interface RegisterGroup {}
public interface UpdateGroup {}

分组其实就是两个空接口,用来做标记用。

然后,我们在 User 实体上应用这些分组:

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

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

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

    @Email(message = "邮箱格式不正确", groups = {RegisterGroup.classUpdateGroup.class})
    private String email
;

    // Getters and setters
}

上面代码中,username 和 email 即属于注册分组也属于更新分组,而 password 则只属于注册分组。

接下来,在注册接口中,我们使用 @Validated 注解并指定 RegisterGroup 分组:

/**
 * @author:江南一点雨
 * @site:http://www.javaboy.org
 * @微信公众号:江南一点雨
 * @github:https://github.com/lenve
 * @gitee:https://gitee.com/lenve
 */

@RestController
public class UserController {
    @GetMapping("/hello")
    public String hello(@Validated UserDto userDto, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            // 处理校验失败情况
        }
        return "200";
    }

    @PostMapping("/register")
    public String register(@Validated(RegisterGroup.class) @RequestBody UserDto user) {
        // 注册逻辑
        return "注册成功";
    }

    @PostMapping("/update")
    public String update(@Validated(UpdateGroup.class) @RequestBody UserDto user) {
        // 更新逻辑
        return "更新成功";
    }
}

在这个例子中,当调用注册接口时,User 对象会根据 RegisterGroup 分组进行校验,而调用更新接口时,则会根据 UpdateGroup 分组进行校验。这样,我们就可以根据不同的业务需求来应用不同的校验规则了。

分组校验这种方式提供了一种灵活的方式来应对不同的校验场景,使得我们的代码更加清晰和易于维护。

四 校验注解

上面松哥和大家演示了几个常用的参数校验注解,完整的校验注解如下:

好啦,这就是松哥和大家介绍的参数校验~公众号后台回复 20241101 可以获取本文完整案例。


微服务项目实战

松哥最近把 AI 面试官项目改造成了微服务,并且录制了配套的视频课程。这次真的是 Buff 叠满:

  • Spring Boot3 + Vue3 + 微信小程序
  • AI
  • 微服务项目

手把手带领小伙伴们做一个 AI 面试官微服务项目。

并且我在这个课程中也会和大家分享如何在简历中写微服务项目以及如何跟面试官去讲自己的微服务项目,让简历更靓。

下面我以问答的形式向小伙伴们介绍下微服务版的 AI 面试官项目。

项目介绍

  • 项目技术栈是什么?

AI 面试官项目采用 Spring Boot3 + Vue3 + uni-app 实现,微服务版基于脚手架完成,涉及到的组件有 Nacos、Gateway、OpenFeign 等。其他的技术细节采用了如 MyBatis-Plus、Redis 等等。

  • 课程内容有哪些?

课程分为了四大部分,前三部分是项目的主体内容,第四部分是项目的赠送内容:

  1. 第一部分是 AI 面试官项目主体,这块我会带领大家手把手开发 AI 面试官项目的后台管理功能和小程序功能,这一阶段的工作完成后,大家的小程序就可以部署上线了,这一阶段大概 13 小时左右。
  2. 第二部分我会和大家一起拆解目前市面上主流的微服务项目脚手架,这一阶段通过对项目架构的分析,让大伙不再觉得微服务是一个高大上的东西,微服务项目是一个人人都可以快速掌握的东西。这一部分大约两小时。
  3. 第三部分我会对前面第一部分已经完成的 AI 面试官项目进行微服务化改造,让大家在理解微服务脚手架的基础之上,真正实践一次微服务。这一部分大约两小时。
  4. 第四部分是赠送给大家的,和大家聊一聊目前比较火的 AI 智能体,带领小伙伴们亲手实践几个 AI 智能体,这一部分大约是两小时。
  • 课程适合谁学?

如果你已经把微服务各个组件掌握的滚瓜烂熟,但是却从没做过微服务项目;如果你想拥抱 AI 但是却只会把 AI 当搜索引擎使用;如果你想把 Java 和 AI 结合起来做个小玩意但是却无从下手;如果你想体验一把 Spring Boot3+Vue3 的最新玩法;那么这个项目适合你。

  • 课程不适合谁学?

如果没有 Java 基础,或者不会 SSM 框架,那么不建议学习这个项目。

  • 我学习过程中遇到问题怎么办?

这个课程松哥提供了微信答疑群,所有课程相关的问题,都可以发到群里讨论,我也都会回复的。

  • 课程只有视频资料吗?

这个课程除了视频之外,还有配套的笔记和代码案例,会一同给到大家。

  • 课程会教小程序上线吗?

会教如何上线小程序,包括小程序审核需要注意的问题,都会发到群里,尽力确保每位小伙伴做完的项目都能成功上线。

  • 小程序做出来是什么样子的?
  • 我能不能先看下课程目录?

AI 面试官课程完整目录

关于松哥

9 年程序员生涯,Java 畅销书作者,华为云最具价值专家,华为开发者社区之星,GitHub 知名项目作者。

目前产品有 Java 项目课程、Java 简历指导、1V1 模拟面试等,如有需求欢迎来勾搭。

感兴趣小伙伴加微信备注 ai

课程目前售价 499,感兴趣小伙伴加松哥微信备注 ai。


江南一点雨
一站式Java全栈技术学习平台!
 最新文章