当前位置:首页>java>不安全的 API,从来不是写错代码,而是“太自信”

不安全的 API,从来不是写错代码,而是“太自信”

  • 2026-10-30 13:51:29
不安全的 API,从来不是写错代码,而是“太自信”

引言

不安全的 API,几乎从来不是写给“粗心开发者”的。

它们往往出自非常自信的开发者之手——
这些人遵循了“正常”的编码习惯,用了主流框架,也加了鉴权。

问题不在于“有没有做安全”,
而在于:太早相信安全已经做完了。


看起来一切都很正常

想象这样一个场景:

你有一个已经上线的 Node.js API。

  • • 用户在注册
  • • 数据在流转
  • • 支付在工作
  • • 日志很干净
  • • 监控没有报错

从外部看,一切都很健康。

这正是大多数 Node.js API 的真实状态。

它们不是“坏掉的”。
它们只是——不安全。

而且这种不安全:

  • • 不会 crash
  • • 不会报警
  • • 不会在测试里暴露

它会安静地躺在那里,
直到某个“不该来的请求”出现。


为什么 Node.js API 会让人“感觉安全”

因为现代框架真的很会营造安全感。

你用了:

  • • Express / NestJS / Fastify
  • • 鉴权中间件
  • • JWT
  • • 路由保护
  • • 一些输入校验

代码结构清晰、职责分明,看起来非常专业。

于是你会下意识认为:

安全这块,我已经 setup 过了。

这很自然。
框架隐藏了复杂度,中间件解决了难题。

但危险也正是从这里开始的。

因为大多数安全问题,并不是因为缺工具,
而是因为代码在工具之后,开始假设太多事情是“可信的”。


真正的问题:后端开始“信任客户端”

绝大多数不安全的 Node.js API,
不是被什么高级攻击击穿的。

而是因为——
后端慢慢开始相信客户端。

来看一个非常真实、非常常见的例子。


❌ 不安全的代码(极其常见)

// PUT /api/profile
app.put("/api/profile", authenticateUser, async (req, res) => {
  const
 { userId, name, email } = req.body;

  await
 User.update(
    { name, email },
    { where: { id: userId } }
  );

  res.json({ success: true });
});

为什么开发者会这样写?

  • • 用户已经鉴权
  • • 前端会传 userId
  • • UI 只允许改自己的资料
  • • 代码看起来很干净

真正的问题是什么?

后端信任了 userId 来自请求体。

攻击者只需要改一下请求:

{
  "userId"
: "1234",
  "name"
: "Hacked",
  "email"
: "hacked@mail.com"
}

如果用户 1234 存在,
他的资料就被改了。

注意几个关键点:

  • • JWT 是合法的
  • • 中间件工作正常
  • • API 没报错

安全是“静默失败”的。


✅ 正确的写法(后端必须决定身份)

app.put("/api/profile", authenticateUser, async (req, res) => {
  const
 { name, email } = req.body;
  const
 authenticatedUserId = req.user.id;

  await
 User.update(
    { name, email },
    { where: { id: authenticatedUserId } }
  );

  res.json({ success: true });
});

一句话原则:

永远不要让客户端告诉你:它在“替谁”操作。


信任是如何一步步渗入代码的?

这些决定,单独看都不吓人:

  • • 请求体是前端校验过的
  • • userId 来自登录态
  • • header 是我们自己设置的

它们:

  • • 节省时间
  • • 减少重复判断
  • • 让代码更“优雅”

但结果是:

后端慢慢不再是“事实来源”。

它不再验证,只是相信。

请把这句话读两遍:
后端开始“相信”,而不是“检查”。


客户端不是你系统的一部分

这一点非常重要,但经常被忽略:

  • • 浏览器可以被修改
  • • 请求可以被重放
  • • ID 可以被篡改
  • • Header 可以被伪造

除非你在代码里显式检查,
否则后端根本分不清“正常请求”和“恶意请求”。

而最危险的地方在于:

什么都不会立刻坏掉。

测试通过。
用户没感觉。
系统继续跑。

于是信任继续增长。


认证 ≠ 授权(最常见的误解)

这是 Node.js API 安全里最被误解的一点。

  • • 认证(Authentication):你是谁
  • • 授权(Authorization):你能干什么

很多 API,只回答了第一个问题。


❌ 不安全的代码(SaaS 中极其常见)

// DELETE /api/orders/:orderId
app.delete("/api/orders/:orderId", authenticateUser, async (req, res) => {
  const
 { orderId } = req.params;

  await
 Order.destroy({
    where
: { id: orderId }
  });

  res.json({ success: true });
});

为什么“感觉安全”?

  • • 用户是登录的
  • • 路由受保护
  • • 看起来很合理

真正缺了什么?

所有权校验。

API 从未问过一句:

这个订单,属于这个用户吗?

只要能猜到 ID,
任何登录用户都能删任何订单。


✅ 加上授权逻辑

app.delete("/api/orders/:orderId", authenticateUser, async (req, res) => {
  const
 { orderId } = req.params;
  const
 userId = req.user.id;

  const
 order = await Order.findOne({
    where
: { id: orderId, userId }
  });

  if
 (!order) {
    return
 res.status(403).json({ error: "Not allowed" });
  }

  await
 order.destroy();
  res.json({ success: true });
});

现实中的不安全代码,通常“看起来很无聊”

比如数据泄露型问题:

app.get("/api/users/:id", authenticateUser, async (req, res) => {
  const
 user = await User.findByPk(req.params.id);
  res.json(user);
});

你可能无意中暴露了:

  • • Email
  • • 内部 ID
  • • 角色
  • • Flags
  • • 甚至密码 hash

今天没事,
明天加一个字段就出事。


✅ 更安全的模式

app.get("/api/users/:id", authenticateUser, async (req, res) => {
  const
 user = await User.findOne({
    where
: { id: req.params.id },
    attributes
: ["id", "name", "avatar"]
  });

  res.json(user);
});

为什么“以后再修”几乎等于不修?

安全债务和技术债务不一样。

  • • 性能问题会慢
  • • Bug 会报错
  • • 安全漏洞什么都不会做

直到它突然变成事故。

随着系统变大:

  • • 权限开始“隐含”
  • • 逻辑分散
  • • 审计成本暴涨

等你真的想修时,
系统已经脆弱到不敢动。


真正安全的 API 是怎么写出来的?

  • • 每个请求都是不可信的
  • • 每个操作都有显式授权
  • • 每个假设都写成代码

这不是偏执,
这是清晰。

而清晰,最终会让系统:

  • • 更好理解
  • • 更好测试
  • • 更好维护

最后的真相

大多数不安全的 Node.js API,
不是出自粗心开发者。

而是出自能力很强、经验丰富、但过于信任自己工具和习惯的人。

Node.js API 安全不是恐惧。
也不是追求完美。

它是一种持续的意识。

最新文章

随机文章