Next.js / Node.js 环境中切断这种大文本拒绝服务攻击(DoS)

在 Next.js / Node.js 的请求生命周期中,中间件就像是守卫在城堡大门口的哨兵。当一个请求从客户端发出,它会先经过中间件,然后才会到达具体的路由处理器(Route Handlers)或服务器组件(Server Components)。

如果等到请求进入了业务代码再去限制体积,Node.js 此时可能已经把这个大文本读入内存了,这无法从根本上防御 DoS 攻击。

在中间件这一层,我们可以利用 HTTP 请求头中的 Content-Length 来做文章。这个请求头记录了整个请求体的字节大小(Byte)。我们可以直接在这里设置一条"硬红线"。

通常,一个正常的导航菜单配置 JSON 数据不会超过几百 KB。如果我们把上限设为 1MB 或者是 2MB,就能挡掉绝大多数恶意的大文件轰炸。

让我们来看看在 Next.js 的 middleware.ts 中可以如何实现这个拦截逻辑:

import 复制代码
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  // 1. 🔍 只针对修改菜单的 POST / PUT 请求进行体积检查
  if (request.nextUrl.pathname.startsWith('/api/admin/menu')) {
    
    // 2. 📏 获取请求头中的数据体积大小 (单位: 字节)
    const contentLength = request.headers.get('content-length');
    
    if (contentLength) {
      const sizeInBytes = parseInt(contentLength, 10);
      const ONE_MEGABYTE = 1024 * 1024; // 1MB
      
      // 3. 🚨 超过红线,直接弹回,不给它进入服务器解析的机会
      if (sizeInBytes > ONE_MEGABYTE) {
        return new NextResponse(
          JSON.stringify({ error: '请求体体积过大,拒绝服务' }),
          { status: 413, headers: { 'content-type': 'application/json' } }
        );
      }
    }
  }

  return NextResponse.next();
}

黑客也可能会非常狡猾。如果黑客故意伪造了 HTTP 请求头,把 Content-Length 故意写得很小(比如写了 100 字节),但实际上底层却通过 HTTP 分块传输(Chunked Transfer Encoding)源源不断地发送了几百兆的数据流,你觉得我们的中间件还能防住它吗?我们还需要配合什么机制来彻底锁死它呢 黑客确实可以通过分块传输来伪造 Content-Length,哨兵在这个时候就会被欺骗。只检查头部是不够的,我们必须对请求体(Request Body)的实际流入量进行实时监控。

在 Node.js 和 Next.js 的路由处理器(Route Handlers)中,标准的做法是流式读取(Streaming)请求体,并在读取过程中计算已接收的字节数。一旦实际读取的字节数超过阈值,立刻掐断连接。

我们可以使用原生的 TransformStream 或者结合 safe-json-parse 来实现这一层防御。

🛡️ 3. 实战:限制实际流入的 Body 体积

在我们的路由处理器中,我们可以写一个通用的解析助手函数:

import 复制代码
import { scan } from 'secure-json-parse';

// 限制实际读取的最大体积为 1MB
const MAX_ALLOWED_SIZE = 1024 * 1024; 

async function parseAndValidateSafeBody(request: NextRequest) {
  const reader = request.body?.getReader();
  if (!reader) throw new Error('请求体为空');

  let totalBytes = 0;
  const chunks: Uint8Array[] = [];

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    totalBytes += value.length;
    
    // 🚨 核心防御:在读取流的过程中,一旦发现实际体积超标,立刻熔断!
    if (totalBytes > MAX_ALLOWED_SIZE) {
      throw new Error('413_PAYLOAD_TOO_LARGE');
    }

    chunks.push(value);
  }

  // 将所有数据块合并并转为字符串
  const concatBuffer = new Uint8Array(totalBytes);
  let offset = 0;
  for (const chunk of chunks) {
    concatBuffer.set(chunk, offset);
    offset += chunk.length;
  }
  
  const rawText = new TextDecoder().decode(concatBuffer);

  // 🔒 结合我们第一维学的 secure-json-parse 杜绝原型链污染
  return scan(rawText, { protoAction: 'error', constructorAction: 'error' });
}
相关推荐
laoli_coding11 小时前
如何配置HTTPS站点访问的免费SSL域名证书
网络协议·https·node.js·ssl
To_OC11 小时前
手写 AI 编程 Agent 的命令执行工具:我被 child_process 坑出来的实战经验
后端·node.js·agent
大家的林语冰19 小时前
👍 Rust 为 Node 插上翅膀,反超 Bun 和 pnpm,一体化工具我也有!
前端·javascript·node.js
Kel21 小时前
Node.js 本质:从全脉络视角理解运行时原理
javascript·设计模式·node.js
ShiXZ2131 天前
指令集-NPM 常用指令速查手册
前端·npm·node.js
孟陬2 天前
高质量全方位的 AI 工具 sse-stuntman — Mock SSE 接口
前端·node.js·openai
掰头战士2 天前
你本地的LLM有时会胡乱使用tools?不妨用MCP规范LLM工具调用!
node.js·llm·全栈
触底反弹2 天前
🧠 LangChain Agent 入门:为什么直接调大模型 API 远远不够?
人工智能·node.js·llm
书中枫叶2 天前
从「上次几点喂奶」到全栈小程序:我做了个喂宝助手
mongodb·微信小程序·node.js
Asize2 天前
Node path 与 fs 模块:从回调地狱到 async await 的异步进化
javascript·node.js