如何保护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
相关产品推荐
相关产品推荐

