
大家好😁。
上个月 Code Review,我拦下了一个新人的代码。
他写了一个转账功能,前端做了极其严密的校验:
- 金额必须是数字。
- 金额必须大于 0。
- 余额不足时,提交按钮是
disabled的。 - 甚至还写了复杂的正则表达式,防止输入负号。
他自信满满地跟我说:老大,放心吧,我前端卡得死死的,用户绝对传不了非法数据。
我笑了笑🤣,没看他的后端代码,直接打开终端,敲了一行命令。
0.5 秒后,他的数据库里多了一笔"-10000"的转账记录,余额瞬间暴涨!
他看着屏幕,目瞪口呆:这......你是怎么做到的?我按钮明明置灰了啊!
今天,我就来揭秘这个所有后端(和全栈)工程师必须铭记的第一铁律:
前端验证,在黑客眼里,只是个小case🤔。
我是如何羞辱前端验证的
假设我们有一个购物网站,前端有一个简单的购买表单。
前端逻辑(看似完美):
JavaScript
// Front-end code
function submitOrder(price, quantity) {
// 1. 校验价格不能被篡改
if (price !== 999) {
alert("价格异常!");
return;
}
// 2. 校验数量必须为正数
if (quantity <= 0) {
alert("数量必须大于0!");
return;
}
// 发送请求
api.post('/buy', { price, quantity });
}
你看,用户在浏览器里确实没法作恶。他改不了价格,也填不了负数。
但是黑客,从来不用浏览器点你的按钮。
第一步:打开DevTools Network 面板,正常点一次购买按钮。捕获到了这个请求。
第二步:请求上右键 -> 复制 -> cURL 格式复制。

这一步,我已经拿到了你发送请求的所有密钥:URL、Headers、Cookies、以及那个看似合法的 Data。
第三步:打开终端(Terminal),粘贴刚才复制的命令。但是,我并没有直接回车。
我修改了 --data-raw 里的参数:
- 把
"price": 999改成了"price": 0.01 - 或者把
"quantity": 1改成了"quantity": -100
Bash
# 经过魔改后的命令
curl 'http://localhost:3000/user/buy' \
-H 'Cookie: session_id=...' \
-H 'Content-Type: application/json' \
--data-raw '{"price": 0.01, "quantity": 10}' \
--compressed
回车!
服务器返回:{ "status": "success", "msg": ok!" }
恭喜你,你的前端验证毫发无损,但你的数据库已经被我击穿了。 我用 1 分钱买了 10 个商品,或者通过负数数量,反向刷了库存。
为什么前端验证, 防不了小人🤔
很多新人最大的误区,就是认为用户只能通过我的 UI 来访问我的服务器。
错!大错特错!
Web 的本质是 HTTP 协议。
HTTP 协议是无状态的、公开的。任何能够发送 HTTP 请求的客户端,都是你的用户。
- Chrome 是客户端。
cURL是客户端。- Postman 是客户端。
- Python 的
requests脚本也是客户端。 - node 的
http脚本也是客户端
前端代码运行在用户的电脑上。
这意味着,用户拥有对前端代码的绝对控制权。
- 他可以禁用 JS。
- 他可以在 Console 里重写你的校验函数。
- 他可以拦截请求(用 Charles/Fiddler)并修改数据。
- 他甚至可以完全抛弃浏览器,直接用脚本轰炸你的 API。
所以,前端验证的唯一作用,是提升用户体验 (比如提示用户格式不对😂),而不是提供安全性😖。
后端该如何防御?(不要裸奔)
既然前端不可信,后端(或 BFF 层)就必须假设所有发过来的数据都是有毒的。
1. 永远不要相信 Payload 里的关键数据
前端只传 productId。后端拿到 ID 后,去数据库里查这个商品到底多少钱。永远以数据库为准。
2. 使用 Schema 校验库(Zod / Joi / class-validator)
不要在 Controller 里写一堆 if (req.body.age < 0)。
使用专业的 Schema 校验库,定义好数据的规则。
TypeScript代码👇:
TypeScript
// 使用 Zod 定义后端校验规则
const OrderSchema = z.object({
productId: z.string(),
// 强制要求 quantity 必须是正整数,拦截 -100 这种攻击
quantity: z.number().int().positive(),
// 注意:这里根本不接收 price 字段,防止被注入
});
// 如果校验失败,直接抛出 400 错误,逻辑根本进不去
const data = OrderSchema.parse(req.body);
3. 权限与状态校验
不要只看数据格式对不对,还要看人对不对。
- 这个用户有权限买这个商品吗?
- 这个订单现在的状态允许支付吗?(防止重复支付攻击🤔)
还有一种更高级的攻击:Replay Attack(重放攻击)
你以为校验了数据就安全了?
如果我拦截了你一次领优惠券的请求,虽然我改不了数据,但我可以用 cURL 连续运行 1000 次这个命令。
Bash
# 一个简单的循环,瞬间刷爆你的接口
for i in {1..1000}; do curl ... ; done
如果你的后端没有做幂等性(Idempotency)校验或频率限制(Rate Limiting) ,那我瞬间就能领走 1000 张优惠券。
防御手段👇:
- Redis 计数器:限制每个 IP/用户 每秒只能请求几次。
- 唯一 Request ID:对于关键操作,要求前端生成一个 UUID,后端处理完后记录下来。如果同一个 UUID 再次请求,直接拒绝。
对于前端安全,所有的输入都是可疑的🤔
作为全栈或后端开发者,当你写 API 时,请忘掉你那个漂亮的前端界面。
你的脑海里应该只有一幅画面:

屏幕对面,不是一个点鼠标的用户,而是一个正在敲 cURL 命令的黑客。
只有这样,你的代码才算真正安全了😒。