title | category | tag | |
---|---|---|---|
为什么前后端都要做数据校验 | 系统设计 |
|
相关面试题:
- 前端做了校验,后端还还需要做校验吗?
- 前端已经做了数据校验,为什么后端还需要再做一遍同样(甚至更严格)的校验呢?
- 前端/后端需要对哪些内容进行校验?
咱们平时做 Web 开发,不管是写前端页面还是后端接口,都离不开跟数据打交道。那怎么保证这些传来传去的数据是靠谱的、安全的呢?这就得靠数据校验了。而且,这活儿,前端得干,后端更得干,还得加上权限校验这道重要的“锁”,缺一不可!
为啥这么说?你想啊,前端校验主要是为了用户体验和挡掉一些明显的“瞎填”数据,但懂点技术的人绕过前端校验简直不要太轻松(比如直接用 Postman 之类的工具发请求)。所以,后端校验才是咱们系统安全和数据准确性的最后一道,也是最硬核的防线。它得确保进到系统里的数据不仅格式对,还得符合业务规矩,最重要的是,执行这个操作的人得有权限!
前端校验就像个贴心的门卫,主要目的是在用户填数据的时候,就赶紧告诉他哪儿不对,让他改,省得提交了半天,结果后端说不行,还得重来。这样做的好处显而易见:
那前端一般都得校验点啥呢?
.jpg
、.png
格式)和文件大小。总之,前端校验的核心是 引导用户正确输入 和 提升交互体验。
前端校验只是第一道防线,虽然提升了用户体验,但毕竟可以被绕过,真正起决定性作用的是后端校验。后端需要对所有前端传来的数据都抱着“可能有问题”的态度,进行全面审查。后端校验不仅要覆盖前端的基本检查(如格式、范围、长度等),还需要更严格、更深入的验证,确保系统的安全性和数据的一致性。以下是后端校验的重点内容:
userId
和 orderId
。如果缺失任何必需字段,后端应立即返回错误,拒绝处理请求。productId
是否存在于数据库中?couponId
是否已经过期或被使用?这通常需要通过查库或调用其他服务来确认。在 Java 后端,每次都手写 if-else 来做这些基础校验太累了。好在 Java 社区给我们提供了 Bean Validation 这套标准规范。它允许我们用注解的方式,直接在 JavaBean(比如我们的 DTO 对象)的属性上声明校验规则,非常方便。
@NotNull
, @Size
, @Min
, @Max
这些老朋友。@NotEmpty
, @NotBlank
, @Email
等更实用的注解。早期的 Spring Boot (大概 2.3.x 之前): spring-boot-starter-web 里自带了 hibernate-validator
,你啥都不用加。
Spring Boot 2.3.x 及之后: 为了更灵活,校验相关的依赖被单独拎出来了。你需要手动添加 spring-boot-starter-validation
依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Bean Validation 规范及其实现(如 Hibernate Validator)提供了丰富的注解,用于声明式地定义校验规则。以下是一些常用的注解及其说明:
@NotNull
: 检查被注解的元素(任意类型)不能为 null
。@NotEmpty
: 检查被注解的元素(如 CharSequence
、Collection
、Map
、Array
)不能为 null
且其大小/长度不能为 0。注意:对于字符串,@NotEmpty
允许包含空白字符的字符串,如 " "
。@NotBlank
: 检查被注解的 CharSequence
(如 String
)不能为 null
,并且去除首尾空格后的长度必须大于 0。(即,不能为空白字符串)。@Null
: 检查被注解的元素必须为 null
。@AssertTrue
/ @AssertFalse
: 检查被注解的 boolean
或 Boolean
类型元素必须为 true
/ false
。@Min(value)
/ @Max(value)
: 检查被注解的数字类型(或其字符串表示)的值必须大于等于 / 小于等于指定的 value
。适用于整数类型(byte
、short
、int
、long
、BigInteger
等)。@DecimalMin(value)
/ @DecimalMax(value)
: 功能类似 @Min
/ @Max
,但适用于包含小数的数字类型(BigDecimal
、BigInteger
、CharSequence
、byte
、short
、int
、long
及其包装类)。 value
必须是数字的字符串表示。@Size(min=, max=)
: 检查被注解的元素(如 CharSequence
、Collection
、Map
、Array
)的大小/长度必须在指定的 min
和 max
范围之内(包含边界)。@Digits(integer=, fraction=)
: 检查被注解的数字类型(或其字符串表示)的值,其整数部分的位数必须 ≤ integer
,小数部分的位数必须 ≤ fraction
。@Pattern(regexp=, flags=)
: 检查被注解的 CharSequence
(如 String
)是否匹配指定的正则表达式 (regexp
)。flags
可以指定匹配模式(如不区分大小写)。@Email
: 检查被注解的 CharSequence
(如 String
)是否符合 Email 格式(内置了一个相对宽松的正则表达式)。@Past
/ @Future
: 检查被注解的日期或时间类型(java.util.Date
、java.util.Calendar
、JSR 310 java.time
包下的类型)是否在当前时间之前 / 之后。@PastOrPresent
/ @FutureOrPresent
: 类似 @Past
/ @Future
,但允许等于当前时间。当 Controller 方法使用 @RequestBody
注解来接收请求体并将其绑定到一个对象时,可以在该参数前添加 @Valid
注解来触发对该对象的校验。如果验证失败,它将抛出MethodArgumentNotValidException
。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
@NotNull(message = "classId 不能为空")
private String classId;
@Size(max = 33)
@NotNull(message = "name 不能为空")
private String name;
@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
@NotNull(message = "sex 不能为空")
private String sex;
@Email(message = "email 格式不正确")
@NotNull(message = "email 不能为空")
private String email;
}
@RestController
@RequestMapping("/api")
public class PersonController {
@PostMapping("/person")
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
return ResponseEntity.ok().body(person);
}
}
对于直接映射到方法参数的简单类型数据(如路径变量 @PathVariable
或请求参数 @RequestParam
),校验方式略有不同:
@Validated
注解:这个注解是 Spring 提供的(非 JSR 标准),它使得 Spring 能够处理方法级别的参数校验注解。这是必需步骤。@Min
, @Max
, @Size
, @Pattern
等校验注解直接应用于对应的 @PathVariable
或 @RequestParam
参数。一定一定不要忘记在类上加上 @Validated
注解了,这个参数可以告诉 Spring 去校验方法参数。
@RestController
@RequestMapping("/api")
@Validated // 关键步骤 1: 必须在类上添加 @Validated
public class PersonController {
@GetMapping("/person/{id}")
public ResponseEntity<Integer> getPersonByID(
@PathVariable("id")
@Max(value = 5, message = "ID 不能超过 5") // 关键步骤 2: 校验注解直接放在参数上
Integer id
) {
// 如果传入的 id > 5,Spring 会在进入方法体前抛出 ConstraintViolationException 异常。
// 全局异常处理器同样需要处理此异常。
return ResponseEntity.ok().body(id);
}
@GetMapping("/person")
public ResponseEntity<String> findPersonByName(
@RequestParam("name")
@NotBlank(message = "姓名不能为空") // 同样适用于 @RequestParam
@Size(max = 10, message = "姓名长度不能超过 10")
String name
) {
return ResponseEntity.ok().body("Found person: " + name);
}
}
Bean Validation 主要解决的是数据格式、语法层面的校验。但光有这个还不够。
数据格式都验过了,没问题。但是,这个操作,当前登录的这个用户,他有权做吗? 这就是权限校验要解决的问题。比如:
权限校验发生在数据校验之后,它关心的是“谁 (Who) 能对 什么资源 (What) 执行 什么操作 (Action)”。
为啥权限校验这么重要?
目前 Java 后端主流的方式是使用成熟的安全框架来实现权限校验,而不是自己手写(容易出错且难以维护)。
权限模型简介:
一般情况下,绝大部分系统都使用的是 RBAC 权限模型或者其简化版本。用一个图来描述如下:
关于权限系统设计的详细介绍,可以看这篇文章:权限系统设计详解。
总而言之,要想构建一个安全、稳定、用户体验好的 Web 应用,前后端数据校验和后端权限校验这三道关卡,都得设好,而且各有侧重:
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。