Skip to content

Commit dbdd3aa

Browse files
committed
[docs add]系统设计-新增文章:为什么前后端都要做数据校验
1 parent 9d11263 commit dbdd3aa

File tree

4 files changed

+214
-14
lines changed

4 files changed

+214
-14
lines changed

README.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -313,15 +313,13 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8](https://docs.oracle.
313313
- [JWT 优缺点分析以及常见问题解决方案](./docs/system-design/security/advantages-and-disadvantages-of-jwt.md)
314314
- [SSO 单点登录详解](./docs/system-design/security/sso-intro.md)
315315
- [权限系统设计详解](./docs/system-design/security/design-of-authority-system.md)
316-
- [常见加密算法总结](./docs/system-design/security/encryption-algorithms.md)
317-
318-
#### 数据脱敏
319316

320-
数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 \* 来代替。
317+
#### 数据安全
321318

322-
#### 敏感词过滤
323-
324-
[敏感词过滤方案总结](./docs/system-design/security/sentive-words-filter.md)
319+
- [常见加密算法总结](./docs/system-design/security/encryption-algorithms.md)
320+
- [敏感词过滤方案总结](./docs/system-design/security/sentive-words-filter.md)
321+
- [数据脱敏方案总结](./docs/system-design/security/data-desensitization.md)
322+
- [为什么前后端都要做数据校验](./docs/system-design/security/data-validation.md)
325323

326324
### 定时任务
327325

docs/.vuepress/sidebar/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ export default sidebar({
469469
"encryption-algorithms",
470470
"sentive-words-filter",
471471
"data-desensitization",
472+
"data-validation",
472473
],
473474
},
474475
"system-design-questions",

docs/home.md

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -299,15 +299,13 @@ JVM 这部分内容主要参考 [JVM 虚拟机规范-Java8](https://docs.oracle.
299299
- [JWT 优缺点分析以及常见问题解决方案](./system-design/security/advantages-and-disadvantages-of-jwt.md)
300300
- [SSO 单点登录详解](./system-design/security/sso-intro.md)
301301
- [权限系统设计详解](./system-design/security/design-of-authority-system.md)
302-
- [常见加密算法总结](./system-design/security/encryption-algorithms.md)
303-
304-
#### 数据脱敏
305302

306-
数据脱敏说的就是我们根据特定的规则对敏感信息数据进行变形,比如我们把手机号、身份证号某些位数使用 \* 来代替。
303+
#### 数据安全
307304

308-
#### 敏感词过滤
309-
310-
[敏感词过滤方案总结](./system-design/security/sentive-words-filter.md)
305+
- [常见加密算法总结](./system-design/security/encryption-algorithms.md)
306+
- [敏感词过滤方案总结](./system-design/security/sentive-words-filter.md)
307+
- [数据脱敏方案总结](./system-design/security/data-desensitization.md)
308+
- [为什么前后端都要做数据校验](./system-design/security/data-validation.md)
311309

312310
### 定时任务
313311

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
---
2+
title: 为什么前后端都要做数据校验
3+
category: 系统设计
4+
tag:
5+
- 安全
6+
---
7+
8+
> 相关面试题:
9+
>
10+
> - 前端做了校验,后端还还需要做校验吗?
11+
> - 前端已经做了数据校验,为什么后端还需要再做一遍同样(甚至更严格)的校验呢?
12+
> - 前端/后端需要对哪些内容进行校验?
13+
14+
咱们平时做 Web 开发,不管是写前端页面还是后端接口,都离不开跟数据打交道。那怎么保证这些传来传去的数据是靠谱的、安全的呢?这就得靠**数据校验**了。而且,这活儿,前端得干,后端**更得干**,还得加上**权限校验**这道重要的“锁”,缺一不可!
15+
16+
为啥这么说?你想啊,前端校验主要是为了用户体验和挡掉一些明显的“瞎填”数据,但懂点技术的人绕过前端校验简直不要太轻松(比如直接用 Postman 之类的工具发请求)。所以,**后端校验才是咱们系统安全和数据准确性的最后一道,也是最硬核的防线**。它得确保进到系统里的数据不仅格式对,还得符合业务规矩,最重要的是,执行这个操作的人得有**权限**
17+
18+
![](https://oss.javaguide.cn/github/javaguide/system-design/security/user-input-validation.png)
19+
20+
## 前端校验
21+
22+
前端校验就像个贴心的门卫,主要目的是在用户填数据的时候,就赶紧告诉他哪儿不对,让他改,省得提交了半天,结果后端说不行,还得重来。这样做的好处显而易见:
23+
24+
1. **用户体验好:** 输入时就有提示,错了马上知道,改起来方便,用户感觉流畅不闹心。
25+
2. **减轻后端压力:** 把一些明显格式错误、必填项没填的数据在前端就拦下来,减少了发往后端的无效请求,省了服务器资源和网络流量。需要注意的是,后端同样还是要校验,只是加上前端校验可以减少很多无效请求。
26+
27+
那前端一般都得校验点啥呢?
28+
29+
- **必填项校验:** 最基本的,该填的地儿可不能空着。
30+
- **格式校验:** 比如邮箱得像个邮箱样儿 ([email protected]),手机号得是 11 位数字等。正则表达式这时候就派上用场了。
31+
- **重复输入校验:** 确保两次输入的内容一致,例如注册时的“确认密码”字段。
32+
- **范围/长度校验:** 年龄不能是负数吧?密码长度得在 6 到 20 位之间吧?这种都得看着。
33+
- **合法性/业务校验:** 比如用户名是不是已经被注册了?选的商品还有没有库存?这得根据具体业务来,需要配合后端来做。
34+
- **文件上传校验:**限制文件类型(如仅支持 `.jpg``.png` 格式)和文件大小。
35+
- **安全性校验:** 防范像 XSS(跨站脚本攻击)这种坏心思,对用户输入的东西做点处理,别让人家写的脚本在咱们页面上跑起来。
36+
- ...等等,根据业务需求来。
37+
38+
总之,前端校验的核心是 **引导用户正确输入****提升交互体验**
39+
40+
## 后端校验
41+
42+
前端校验只是第一道防线,虽然提升了用户体验,但毕竟可以被绕过,真正起决定性作用的是后端校验。后端需要对所有前端传来的数据都抱着“可能有问题”的态度,进行全面审查。后端校验不仅要覆盖前端的基本检查(如格式、范围、长度等),还需要更严格、更深入的验证,确保系统的安全性和数据的一致性。以下是后端校验的重点内容:
43+
44+
1. **完整性校验:** 接口文档中明确要求的字段必须存在,例如 `userId``orderId`。如果缺失任何必需字段,后端应立即返回错误,拒绝处理请求。
45+
2. **合法性/存在性校验:** 验证传入的数据是否真实有效。例如,传过来的 `productId` 是否存在于数据库中?`couponId` 是否已经过期或被使用?这通常需要通过查库或调用其他服务来确认。
46+
3. **一致性校验:** 针对涉及多个数据对象的操作,验证它们是否符合业务逻辑。例如,更新订单状态前,需要确保订单的当前状态允许修改,不能直接从“未支付”跳到“已完成”。一致性校验是保证数据流转正确性的关键。
47+
4. **安全性校验:** 后端必须防范各种恶意攻击,包括但不限于 XSS、SQL 注入等。所有外部输入都应进行严格的过滤和验证,例如使用参数化查询防止 SQL 注入,或对返回的 HTML 数据进行转义,避免跨站脚本攻击。
48+
5. ...基本上,前端能做的校验,后端为了安全都得再来一遍。
49+
50+
在 Java 后端,每次都手写 if-else 来做这些基础校验太累了。好在 Java 社区给我们提供了 **Bean Validation** 这套标准规范。它允许我们用**注解**的方式,直接在 JavaBean(比如我们的 DTO 对象)的属性上声明校验规则,非常方便。
51+
52+
- **JSR 303 (1.0):** 打下了基础,引入了 `@NotNull`, `@Size`, `@Min`, `@Max` 这些老朋友。
53+
- **JSR 349 (1.1):** 增加了对方法参数和返回值的校验,还有分组校验等增强。
54+
- **JSR 380 (2.0):** 拥抱 Java 8,支持了新的日期时间 API,还加了 `@NotEmpty`, `@NotBlank`, `@Email` 等更实用的注解。
55+
56+
早期的 Spring Boot (大概 2.3.x 之前): spring-boot-starter-web 里自带了 `hibernate-validator`,你啥都不用加。
57+
58+
Spring Boot 2.3.x 及之后: 为了更灵活,校验相关的依赖被单独拎出来了。你需要手动添加 `spring-boot-starter-validation` 依赖:
59+
60+
```xml
61+
<dependency>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-starter-validation</artifactId>
64+
</dependency>
65+
```
66+
67+
Bean Validation 规范及其实现(如 Hibernate Validator)提供了丰富的注解,用于声明式地定义校验规则。以下是一些常用的注解及其说明:
68+
69+
- `@NotNull`: 检查被注解的元素(任意类型)不能为 `null`
70+
- `@NotEmpty`: 检查被注解的元素(如 `CharSequence``Collection``Map``Array`)不能为 `null` 且其大小/长度不能为 0。注意:对于字符串,`@NotEmpty` 允许包含空白字符的字符串,如 `" "`
71+
- `@NotBlank`: 检查被注解的 `CharSequence`(如 `String`)不能为 `null`,并且去除首尾空格后的长度必须大于 0。(即,不能为空白字符串)。
72+
- `@Null`: 检查被注解的元素必须为 `null`
73+
- `@AssertTrue` / `@AssertFalse`: 检查被注解的 `boolean``Boolean` 类型元素必须为 `true` / `false`
74+
- `@Min(value)` / `@Max(value)`: 检查被注解的数字类型(或其字符串表示)的值必须大于等于 / 小于等于指定的 `value`。适用于整数类型(`byte``short``int``long``BigInteger` 等)。
75+
- `@DecimalMin(value)` / `@DecimalMax(value)`: 功能类似 `@Min` / `@Max`,但适用于包含小数的数字类型(`BigDecimal``BigInteger``CharSequence``byte``short``int``long`及其包装类)。 `value` 必须是数字的字符串表示。
76+
- `@Size(min=, max=)`: 检查被注解的元素(如 `CharSequence``Collection``Map``Array`)的大小/长度必须在指定的 `min``max` 范围之内(包含边界)。
77+
- `@Digits(integer=, fraction=)`: 检查被注解的数字类型(或其字符串表示)的值,其整数部分的位数必须 ≤ `integer`,小数部分的位数必须 ≤ `fraction`
78+
- `@Pattern(regexp=, flags=)`: 检查被注解的 `CharSequence`(如 `String`)是否匹配指定的正则表达式 (`regexp`)。`flags` 可以指定匹配模式(如不区分大小写)。
79+
- `@Email`: 检查被注解的 `CharSequence`(如 `String`)是否符合 Email 格式(内置了一个相对宽松的正则表达式)。
80+
- `@Past` / `@Future`: 检查被注解的日期或时间类型(`java.util.Date``java.util.Calendar`、JSR 310 `java.time` 包下的类型)是否在当前时间之前 / 之后。
81+
- `@PastOrPresent` / `@FutureOrPresent`: 类似 `@Past` / `@Future`,但允许等于当前时间。
82+
- ......
83+
84+
当 Controller 方法使用 `@RequestBody` 注解来接收请求体并将其绑定到一个对象时,可以在该参数前添加 `@Valid` 注解来触发对该对象的校验。如果验证失败,它将抛出`MethodArgumentNotValidException`
85+
86+
```java
87+
@Data
88+
@AllArgsConstructor
89+
@NoArgsConstructor
90+
public class Person {
91+
@NotNull(message = "classId 不能为空")
92+
private String classId;
93+
94+
@Size(max = 33)
95+
@NotNull(message = "name 不能为空")
96+
private String name;
97+
98+
@Pattern(regexp = "((^Man$|^Woman$|^UGM$))", message = "sex 值不在可选范围")
99+
@NotNull(message = "sex 不能为空")
100+
private String sex;
101+
102+
@Email(message = "email 格式不正确")
103+
@NotNull(message = "email 不能为空")
104+
private String email;
105+
}
106+
107+
108+
@RestController
109+
@RequestMapping("/api")
110+
public class PersonController {
111+
@PostMapping("/person")
112+
public ResponseEntity<Person> getPerson(@RequestBody @Valid Person person) {
113+
return ResponseEntity.ok().body(person);
114+
}
115+
}
116+
```
117+
118+
对于直接映射到方法参数的简单类型数据(如路径变量 `@PathVariable` 或请求参数 `@RequestParam`),校验方式略有不同:
119+
120+
1. **在 Controller 类上添加 `@Validated` 注解**:这个注解是 Spring 提供的(非 JSR 标准),它使得 Spring 能够处理方法级别的参数校验注解。**这是必需步骤。**
121+
2. **将校验注解直接放在方法参数上**:将 `@Min`, `@Max`, `@Size`, `@Pattern` 等校验注解直接应用于对应的 `@PathVariable``@RequestParam` 参数。
122+
123+
一定一定不要忘记在类上加上 `@Validated` 注解了,这个参数可以告诉 Spring 去校验方法参数。
124+
125+
```java
126+
@RestController
127+
@RequestMapping("/api")
128+
@Validated // 关键步骤 1: 必须在类上添加 @Validated
129+
public class PersonController {
130+
131+
@GetMapping("/person/{id}")
132+
public ResponseEntity<Integer> getPersonByID(
133+
@PathVariable("id")
134+
@Max(value = 5, message = "ID 不能超过 5") // 关键步骤 2: 校验注解直接放在参数上
135+
Integer id
136+
) {
137+
// 如果传入的 id > 5,Spring 会在进入方法体前抛出 ConstraintViolationException 异常。
138+
// 全局异常处理器同样需要处理此异常。
139+
return ResponseEntity.ok().body(id);
140+
}
141+
142+
@GetMapping("/person")
143+
public ResponseEntity<String> findPersonByName(
144+
@RequestParam("name")
145+
@NotBlank(message = "姓名不能为空") // 同样适用于 @RequestParam
146+
@Size(max = 10, message = "姓名长度不能超过 10")
147+
String name
148+
) {
149+
return ResponseEntity.ok().body("Found person: " + name);
150+
}
151+
}
152+
```
153+
154+
Bean Validation 主要解决的是**数据格式、语法层面**的校验。但光有这个还不够。
155+
156+
## 权限校验
157+
158+
数据格式都验过了,没问题。但是,**这个操作,当前登录的这个用户,他有权做吗?** 这就是**权限校验**要解决的问题。比如:
159+
160+
- 普通用户能修改别人的订单吗?(不行)
161+
- 游客能访问管理员后台接口吗?(不行)
162+
- 游客能管理其他用户的信息吗?(不行)
163+
- VIP 用户能使用专属的优惠券吗?(可以)
164+
- ......
165+
166+
权限校验发生在**数据校验之后**,它关心的是“**谁 (Who)** 能对 **什么资源 (What)** 执行 **什么操作 (Action)**”。
167+
168+
**为啥权限校验这么重要?**
169+
170+
- **安全基石:** 防止未经授权的访问和操作,保护用户数据和系统安全。
171+
- **业务隔离:** 确保不同角色(管理员、普通用户、VIP 用户等)只能访问和操作其权限范围内的功能。
172+
- **合规要求:** 很多行业法规对数据访问权限有严格要求。
173+
174+
目前 Java 后端主流的方式是使用成熟的安全框架来实现权限校验,而不是自己手写(容易出错且难以维护)。
175+
176+
1. **Spring Security (业界标准,推荐):** 基于过滤器链(Filter Chain)拦截请求,进行认证(Authentication - 你是谁?)和授权(Authorization - 你能干啥?)。Spring Security 功能强大、社区活跃、与 Spring 生态无缝集成。不过,配置相对复杂,学习曲线较陡峭。
177+
2. **Apache Shiro:** 另一个流行的安全框架,相对 Spring Security 更轻量级,API 更直观易懂。同样提供认证、授权、会话管理、加密等功能。对于不熟悉 Spring 或觉得 Spring Security 太重的项目,是一个不错的选择。
178+
3. **Sa-Token:** 国产的轻量级 Java 权限认证框架。支持认证授权、单点登录、踢人下线、自动续签等功能。相比于 Spring Security 和 Shiro 来说,Sa-Token 内置的开箱即用的功能更多,使用也更简单。
179+
4. **手动检查 (不推荐用于复杂场景):** 在 Service 层或 Controller 层代码里,手动获取当前用户信息(例如从 SecurityContextHolder 或 Session 中),然后 if-else 判断用户角色或权限。权限逻辑与业务逻辑耦合、代码重复、难以维护、容易遗漏。只适用于非常简单的权限场景。
180+
181+
**权限模型简介:**
182+
183+
- **RBAC (Role-Based Access Control):** 基于角色的访问控制。给用户分配角色,给角色分配权限。用户拥有其所有角色的权限总和。这是最常见的模型。
184+
- **ABAC (Attribute-Based Access Control):** 基于属性的访问控制。决策基于用户属性、资源属性、操作属性和环境属性。更灵活但也更复杂。
185+
186+
一般情况下,绝大部分系统都使用的是 RBAC 权限模型或者其简化版本。用一个图来描述如下:
187+
188+
![RBAC 权限模型示意图](https://oss.javaguide.cn/github/javaguide/system-design/security/design-of-authority-system/rbac.png)
189+
190+
关于权限系统设计的详细介绍,可以看这篇文章:[权限系统设计详解](https://javaguide.cn/system-design/security/design-of-authority-system.html)
191+
192+
## 总结
193+
194+
总而言之,要想构建一个安全、稳定、用户体验好的 Web 应用,前后端数据校验和后端权限校验这三道关卡,都得设好,而且各有侧重:
195+
196+
- **前端数据校验:** 提升用户体验,减少无效请求,是第一道“友好”的防线。
197+
- **后端数据校验:** 保证数据格式正确、符合业务规则,是防止“脏数据”入库的“技术”防线。 Bean Validation 允许我们用注解的方式,直接在 JavaBean(比如我们的 DTO 对象)的属性上声明校验规则,非常方便。
198+
- **后端权限校验:** 确保“对的人”做“对的事”,是防止越权操作的“安全”防线。Spring Security、Shiro、Sa-Token 等框架可以帮助我们实现权限校验。
199+
200+
## 参考
201+
202+
- 为什么前后端都需要进行数据校验?: <https://juejin.cn/post/7306045519099658240>
203+
- 权限系统设计详解:<https://javaguide.cn/system-design/security/design-of-authority-system.html>

0 commit comments

Comments
 (0)