You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何保护NextJS 14 Route Handlers,避免被网站外部访问?

阻止Next.js 14 Route Handlers被外部访问的方案

你遇到的问题核心是:同源策略(SOP)和CORS仅由浏览器强制执行,像Postman这类工具不受限制,所以必须在服务端层面添加验证逻辑来拦截非内部请求。以下是几种可行的方案:

1. 验证请求来源头(Origin/Referer)

通过检查请求的Origin或Referer头,只允许来自你自己域名的请求。虽然这两个头可以被篡改,但能挡住绝大多数普通外部访问。

修改你的api/translate-text/route.ts:

import { NextResponse } from 'next/server';

const ALLOWED_DOMAINS = ['https://your-production-domain.com', 'http://localhost:3000']; // 替换成你的域名

export async function POST(request: Request) {
  // 验证请求来源
  const origin = request.headers.get('origin');
  const referer = request.headers.get('referer');
  
  let isAllowed = false;
  if (origin && ALLOWED_DOMAINS.includes(origin)) {
    isAllowed = true;
  } else if (referer) {
    const refererDomain = new URL(referer).origin;
    isAllowed = ALLOWED_DOMAINS.includes(refererDomain);
  }

  if (!isAllowed) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
  }

  // 原有业务逻辑
  const { text} = await request.json();

  const res = await fetch(
    `https://api-source.com`,
    {
      method: "POST",
      headers: {
        "content-type": "application/x-www-form-urlencoded",
        "Key": process.env.API_KEY || "",
        "Host": process.env.API_HOST || "",
      },
      body: new URLSearchParams({
        from: "auto",
        to: "en",
        text: text,
      }),
    }
  );

  const translatedText = await res.json();
  return NextResponse.json(translatedText, {
    status: 201,
  });
}

2. 使用CSRF令牌验证

CSRF令牌是更可靠的方式,只有从你的页面发起的请求才能拿到有效令牌。

步骤1:生成CSRF令牌(服务端组件中)

// app/page.tsx(服务端组件)
import { randomUUID } from 'crypto';
import Form from './Form';

export default function Home() {
  const csrfToken = randomUUID();
  return (
    <div>
      <Form csrfToken={csrfToken} />
    </div>
  );
}

步骤2:客户端请求携带令牌

修改Form.tsx:

// Client component || Form.tsx
type Props = { csrfToken: string };

async function sendText(text: string, csrfToken: string) {
  const translateResponse = await fetch("/api/translate-text", {
    method: "POST",
    headers: { 
      "Content-Type": "application/json",
      "X-CSRF-Token": csrfToken
    },
    body: JSON.stringify({
      text: text,
    }),
  });

  const translatedData = await translateResponse.json();
}

export default function Form({ csrfToken }: Props) {
  return <button onClick={() => sendText('test', csrfToken)}>翻译</button>;
}

步骤3:服务端验证令牌

修改api/translate-text/route.ts:

import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const csrfToken = request.headers.get('X-CSRF-Token');
  // 生产环境建议将令牌存在Session或加密Cookie中,这里简化逻辑
  if (!csrfToken) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
  }

  // 原有业务逻辑...
}

3. 自定义API密钥验证

给内部请求添加自定义API密钥,客户端请求时携带,服务端校验是否匹配环境变量中的密钥。

步骤1:客户端请求携带密钥

修改Form.tsx:

async function sendText(text: string) {
  const translateResponse = await fetch("/api/translate-text", {
    method: "POST",
    headers: { 
      "Content-Type": "application/json",
      "X-Internal-Key": process.env.NEXT_PUBLIC_INTERNAL_API_KEY || ""
    },
    body: JSON.stringify({
      text: text,
    }),
  });

  const translatedData = await translateResponse.json();
}

步骤2:服务端验证密钥

修改api/translate-text/route.ts:

import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const internalKey = request.headers.get('X-Internal-Key');
  if (!internalKey || internalKey !== process.env.INTERNAL_API_KEY) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 403 });
  }

  // 原有业务逻辑...
}

注意:INTERNAL_API_KEY不要加NEXT_PUBLIC_前缀,仅在服务端可见。

4. 组合多种方案

生产环境建议同时使用多种验证方式(比如来源头验证 + CSRF令牌),进一步提升安全性。

内容的提问来源于stack exchange,提问作者Remy Jouni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:14:56