Next.js API 认证与安全:从 JWT 到速率限制的完整实战指南

凌晨三点,手机震了。是云服务商的账单提醒——7800美元。
我揉揉眼睛,以为看错了。上个月账单才120块。点开详情,API 调用次数:1800万次。我的个人项目,平时一天也就几百次调用。
原来是某个爬虫发现了我没加任何防护的 API 端点,整整刷了三天。那一刻我突然明白,为什么都说 API 安全不是可选项。
说真的,很多人(包括之前的我)搭 Next.js 项目时,只关心页面好不好看、交互流不流畅,觉得 API 安全是后端的事。但 Next.js 的 API Routes 本质上就是你的后端,你不保护它,谁来保护?
这篇文章,我想把这几年踩过的坑、研究过的方案,系统地整理一遍。从 JWT 认证到 CORS 配置,从速率限制到输入验证——不是空谈理论,而是能直接用在项目里的实战代码。
为什么 API 安全这么重要
API 安全的常见威胁
去年12月,React 官方发了个严重安全公告,编号 CVE-2025-55182,CVSS 评分直接拉满——10.0。
这是什么概念?满分。攻击者只要构造一个特殊的 HTTP 请求,就能在你的服务器上执行任意代码。如果你用的是 React Server Components,没及时更新,基本就是在裸奔。
这还不是个例。今年3月又出了个授权绕过漏洞 (CVE-2025-29927),评分 9.1。攻击者伪造一个请求头,就能绕过你的中间件鉴权。你以为设了认证就安全?抱歉,人家直接跳过。
除了这些高危漏洞,日常威胁更多:
恶意爬虫和 DDoS 攻击。没做限流的 API,分分钟被刷垮。我见过有人的登录接口一秒被请求3000次,暴力破解密码,服务器直接宕机。
数据泄露。没做好权限控制,用户 A 能看到用户 B 的订单信息。这种事上了新闻,品牌声誉直接完蛋。
注入攻击。SQL 注入、XSS、命令注入…听着老套,但每年还是有无数项目中招。你以为”我用 React 自动转义了”,但 API 这边没验证,照样白搭。
Next.js API Routes 的特点
Next.js 的 API Routes 跟传统后端不太一样,有些特点要注意:
Serverless 优先。部署到 Vercel 的话,每个 API 请求都是一个独立的 Serverless 函数。好处是自动扩展,坏处是无状态——你不能用传统的内存 Session,得换成 JWT 或数据库 Session。
和前端混在一起。代码都在一个仓库,环境变量一不小心就泄露到客户端。我见过有人把 DATABASE_URL 写在 .env 里,结果打包时被暴露到前端 bundle,直接上了 GitHub。
边缘计算的限制。如果你用 Edge Runtime,有些 Node.js API 用不了,加密库、数据库连接都要重新选。这时候安全方案也得跟着调整。
说白了,Next.js API 是你的后端,但它更轻、更灵活、也更容易出问题。
API 认证实战
认证方案选择指南
先聊个最实际的问题:JWT 和 Session,到底该用哪个?
我刚开始做 Next.js 项目时,也纠结过这个。网上说法一堆,有人说 JWT 是现代方案必须用,有人说 Session 更安全。后来我发现,这事得看场景。
JWT 适合这些情况:
- 你的应用要部署到多个服务器 (Serverless、边缘节点)
- 需要跨域认证,比如前端在 app.com,API 在 api.com
- 不想管理 Session 存储,越简单越好
JWT 的本质就是把用户信息编码成一个 token,服务器不存状态,每次请求带上 token 就行。横向扩展超级方便。
Session 适合这些情况:
- 需要服务端主动控制,比如踢人下线、权限实时变更
- 对安全要求极高,不想让客户端持有任何用户信息
- 已经有 Redis 或数据库,管理 Session 不是问题
Session 的特点是状态在服务端,客户端只有个 Session ID。你想让某个用户失效,删掉 Session 就行,JWT 做不到这点。
我自己的选择标准:小项目、个人项目用 JWT,省事;企业级、需要精细控制的用 Session。别纠结,先上手一个,遇到问题再换也来得及。
JWT 认证完整实现
好,假设你决定用 JWT。怎么在 Next.js 里实现?
第一步:生成和验证 Token
先装个库:
npm install jose为什么不用 jsonwebtoken?那个库不支持 Edge Runtime。jose 是 Web 标准实现,啥环境都能跑。
创建 lib/auth.ts:
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(
process.env.JWT_SECRET || 'your-secret-key-at-least-32-characters'
);
export async function createToken(payload: { userId: string }) {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('15m') // 15分钟后过期
.sign(secret);
}
export async function verifyToken(token: string) {
try {
const { payload } = await jwtVerify(token, secret);
return payload;
} catch {
return null;
}
}注意那个 15m 过期时间。很多人设成 7 天、30 天,方便是方便,但 token 一旦泄露,攻击者能用很久。短期 Access Token + 长期 Refresh Token,才是正经做法。
第二步:存储 Token——别用 localStorage
这是重点。网上很多教程教你把 token 存 localStorage,然后 XSS 攻击一来,token 全被偷走。
正确做法:HttpOnly Cookie。
登录接口返回 token 时:
// app/api/login/route.ts
import { NextResponse } from 'next/server';
import { createToken } from '@/lib/auth';
export async function POST(request: Request) {
// 验证用户名密码...
const token = await createToken({ userId: user.id });
const response = NextResponse.json({ success: true });
response.cookies.set('token', token, {
httpOnly: true, // JS 读不到,防 XSS
secure: true, // 只能 HTTPS 传输
sameSite: 'lax', // 防 CSRF
maxAge: 900, // 15分钟,和 token 过期时间一致
});
return response;
}HttpOnly 是关键:JavaScript 完全读不到这个 Cookie,XSS 攻击拿不走。
第三步:中间件保护 API
现在 token 有了,怎么让某些 API 必须登录才能访问?用 Next.js 的 Middleware。
创建 middleware.ts:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifyToken } from './lib/auth';
export async function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value;
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const payload = await verifyToken(token);
if (!payload) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 }
);
}
// 验证通过,把用户信息传给 API
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-user-id', payload.userId as string);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
export const config = {
matcher: '/api/protected/:path*',
};这样一来,所有 /api/protected/* 的接口都自动受保护了。在 API 里取用户信息:
// app/api/protected/profile/route.ts
import { headers } from 'next/headers';
export async function GET() {
const headersList = await headers();
const userId = headersList.get('x-user-id');
// 去数据库查用户信息...
}第四步:Token 刷新机制
15分钟就过期,用户不得频繁登录?这里要引入 Refresh Token。
Access Token 短期 (15分钟),Refresh Token 长期 (30天)。Access Token 过期了,用 Refresh Token 换一个新的,不用重新登录。
具体实现稍微复杂点,但思路就是这样。网上有很多现成方案,比如 next-auth 内置了这套逻辑。
使用 NextAuth.js 快速集成
说实话,上面那套自己写一遍,能理解原理,但工程量不小。如果你想快速上手,直接用 NextAuth.js (现在叫 Auth.js)。
这库牛在哪?开箱即用的安全默认配置:
- 自动处理 CSRF 保护
- Session 签名和加密
- 支持 JWT 和数据库 Session
- 自带 Google、GitHub 等第三方登录
装一下:
npm install next-auth创建 app/api/auth/[...nextauth]/route.ts:
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
const handler = NextAuth({
providers: [
CredentialsProvider({
name: 'Credentials',
credentials: {
email: { label: "Email", type: "email" },
password: { label: "Password", type: "password" }
},
async authorize(credentials) {
// 验证用户名密码...
if (user) {
return { id: user.id, email: user.email };
}
return null;
}
})
],
session: {
strategy: 'jwt', // 用 JWT,适合 Serverless
maxAge: 30 * 24 * 60 * 60, // 30 天
},
callbacks: {
async jwt({ token, user }) {
if (user) {
token.userId = user.id;
}
return token;
},
async session({ session, token }) {
session.userId = token.userId;
return session;
}
}
});
export { handler as GET, handler as POST };然后在 API 里检查登录状态:
import { getServerSession } from 'next-auth';
export async function GET() {
const session = await getServerSession();
if (!session) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// 已登录,继续处理...
}就这么简单。NextAuth 帮你把 token 管理、Session 刷新全搞定了。
CORS 配置详解
CORS 的本质和常见问题
CORS(跨域资源共享)是个让很多人头疼的东西。开发环境好好的,一部署就报错:
Access to fetch at 'https://api.example.com' from origin 'https://app.example.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.简单讲,浏览器有个安全策略:网页 A 不能随便访问网站 B 的资源。比如你在 app.com 的页面,想调 api.com 的接口,浏览器会先问 api.com:“这个请求来自 app.com,你同意吗?”API 必须明确回复”我同意”,请求才能通过。
为什么开发环境不报错?
Next.js 开发时,前端和 API 都在 localhost:3000,同源,不涉及跨域。一部署,前端在 Vercel,API 在别的服务器,就跨域了。
Preflight 请求是啥?
你发个 POST 请求,携带自定义请求头 (比如 Authorization),浏览器会先发个 OPTIONS 请求试探一下。这就是 Preflight。API 如果没处理 OPTIONS,直接 404,CORS 就失败了。
我之前就踩过这坑:写好了 POST 接口,但忘了处理 OPTIONS,前端一直报 CORS 错误,找了半天才发现。
Next.js 中配置 CORS 的三种方法
方法一:next.config.js 全局配置
适合所有 API 都允许同样的跨域来源。
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: 'https://app.example.com' },
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
],
},
];
},
};优点:一次配置,全局生效。缺点:不够灵活,不能针对单个 API 定制。
方法二:Middleware 中间件配置
适合需要动态判断、统一处理的场景。
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 处理 Preflight 请求
if (request.method === 'OPTIONS') {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
// 正常请求,添加 CORS 头
const response = NextResponse.next();
response.headers.set('Access-Control-Allow-Origin', 'https://app.example.com');
return response;
}
export const config = {
matcher: '/api/:path*',
};这种方式能拿到 request 对象,可以根据来源动态决定是否允许。
方法三:API Route 内配置
适合单个 API 有特殊需求的情况。
// app/api/public/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const data = { message: 'Hello' };
return NextResponse.json(data, {
headers: {
'Access-Control-Allow-Origin': '*', // 公开 API,允许所有来源
},
});
}
export async function OPTIONS() {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}注意每个 API 都要写 OPTIONS 处理,不然 Preflight 过不了。
CORS 安全最佳实践
1. 不要乱用通配符
看到很多人这么写:
'Access-Control-Allow-Origin': '*'这意味着任何网站都能调你的 API。公开数据无所谓,但如果涉及用户信息、敏感操作,这就是在裸奔。
正确做法:明确指定允许的域名。
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
const origin = request.headers.get('origin');
if (origin && allowedOrigins.includes(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin);
}2. 凭证传递要小心
如果你的 API 需要读 Cookie (比如 Session 认证),前端要这么写:
fetch('https://api.example.com', {
credentials: 'include',
});后端必须配合:
response.headers.set('Access-Control-Allow-Credentials', 'true');但是,Access-Control-Allow-Origin 不能是 *。浏览器直接拒绝这种组合,你必须指定具体域名。
3. 处理 Preflight 请求
记住:带 Authorization 头、Content-Type: application/json 的请求,基本都会触发 Preflight。你的 API 必须响应 OPTIONS 方法。
可以写个通用函数:
export function corsHeaders(origin?: string) {
return {
'Access-Control-Allow-Origin': origin || 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400', // Preflight 结果缓存 24 小时
};
}然后每个 API 复用就行了。
API 速率限制
为什么需要速率限制
回到开头那个故事。我的 API 被刷了1800万次,如果当时做了限流,每个 IP 每分钟最多请求100次,损失可能就几十块,不会是7800美元。
速率限制(Rate Limiting)就是限制用户在一定时间内能调用多少次 API。听着简单,作用可不小:
防 DDoS 攻击。攻击者想用海量请求把你服务器冲垮?限流一开,每秒超过阈值的请求直接拒绝,服务器稳如狗。
防暴力破解。登录接口不限流,黑客写个脚本一秒试10000个密码。限流后,每个 IP 每分钟只能尝试5次,破解难度指数级上升。
保护资源。你的数据库、第三方 API 调用都有成本。限流能防止单个用户把资源耗光,保证服务对所有人公平。
速率限制方案对比
Next.js 做限流,主流有几个方案:
方案一:@upstash/ratelimit + Vercel KV
这是我现在最常用的方案。Upstash 是个 Serverless Redis,Vercel 官方合作伙伴,集成超级简单。
优点:
- Serverless 友好,不用自己管 Redis 服务器
- 支持多种算法:固定窗口、滑动窗口、令牌桶
- 免费额度够个人项目用
缺点:
- 大流量项目需要付费
- 依赖第三方服务
方案二:自托管 Redis
如果你已经有 Redis,或者不想依赖第三方,可以自己实现。
优点:
- 完全控制,没有额外费用
- 可以定制各种复杂逻辑
缺点:
- 需要维护 Redis 服务器
- Serverless 环境下配置复杂
方案三:内存限流
不想装 Redis?纯内存也能实现简单限流。
优点:
- 零依赖,代码几行搞定
- 适合开发环境和小项目
缺点:
- Serverless 环境每次请求可能是新实例,内存不共享,限流失效
- 重启服务器限流数据全丢
我的建议:个人项目、Serverless 部署,直接上 Upstash;企业项目、自己管服务器,用 Redis;Demo 或本地开发,内存方案凑合。
实战代码示例
以 Upstash 为例,手把手教你实现。
第一步:安装和配置
npm install @upstash/ratelimit @upstash/redis去 Upstash 官网创建个 Redis 数据库,拿到 UPSTASH_REDIS_REST_URL 和 UPSTASH_REDIS_REST_TOKEN,放进 .env:
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token第二步:创建限流器
创建 lib/rate-limit.ts:
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
// 创建 Redis 客户端
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// 创建限流器:滑动窗口,10秒内最多10次请求
export const ratelimit = new Ratelimit({
redis,
limiter: Ratelimit.slidingWindow(10, '10 s'),
analytics: true,
});slidingWindow(10, '10 s') 意思是:10秒内最多10次请求。滑动窗口比固定窗口更平滑,不会出现窗口边界突刺。
第三步:在 API 中使用
// app/api/protected/route.ts
import { NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';
export async function GET(request: Request) {
// 获取用户 IP
const ip = request.headers.get('x-forwarded-for') || 'unknown';
// 检查限流
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{
error: 'Too many requests',
limit,
remaining,
reset: new Date(reset),
},
{
status: 429,
headers: {
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
);
}
// 限流通过,正常处理
return NextResponse.json({ data: 'Success' });
}这里用 IP 作为限流标识。如果你有用户登录,可以用 userId:
const identifier = session?.userId || ip;
const { success } = await ratelimit.limit(identifier);这样登录用户按用户限流,未登录按 IP 限流,更精准。
第四步:中间件全局限流
不想每个 API 都写一遍?在 Middleware 里统一处理:
// middleware.ts
import { ratelimit } from '@/lib/rate-limit';
export async function middleware(request: NextRequest) {
const ip = request.ip || 'unknown';
const { success } = await ratelimit.limit(ip);
if (!success) {
return NextResponse.json(
{ error: 'Too many requests' },
{ status: 429 }
);
}
return NextResponse.next();
}
export const config = {
matcher: '/api/:path*',
};所有 API 自动受保护,省事。
输入验证与防御
为什么输入验证是第一道防线
“永远不要信任用户输入”——这是安全领域的金科玉律。
你的前端有各种表单验证?没用。打开开发者工具,改改代码,验证直接绕过。真正的防御在服务端。
SQL 注入。用户在输入框写 '; DROP TABLE users; --,如果你直接拼接 SQL,数据库就炸了。虽然现在大家都用 ORM,但原生 SQL 场景还是很多。
XSS 攻击。用户提交 <script>alert('hacked')</script>,你存到数据库,其他用户打开页面,脚本执行,Cookie 被偷。React 确实会自动转义,但如果你用 dangerouslySetInnerHTML,照样中招。
DoS 攻击。用户提交个10MB 的 JSON,你的 Serverless 函数内存直接爆掉。或者发个超长字符串,正则表达式回溯到天荒地老。
输入验证能挡住绝大部分低级攻击。不验证,再多防御措施都是筛子。
使用 Zod 进行类型安全验证
Zod 是我现在最爱用的验证库。TypeScript 写的,和类型系统完美配合。
装一下:
npm install zod基础用法
定义一个 schema:
import { z } from 'zod';
const userSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
age: z.number().int().min(18).max(120),
});在 API 里验证:
// app/api/register/route.ts
import { NextResponse } from 'next/server';
import { userSchema } from '@/lib/schemas';
export async function POST(request: Request) {
const body = await request.json();
// 验证数据
const result = userSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{
error: 'Validation failed',
details: result.error.format(),
},
{ status: 400 }
);
}
// 验证通过,拿到类型安全的数据
const { email, password, age } = result.data;
// 继续处理...
}注意用 safeParse,不会抛异常。parse 会抛异常,你得 try-catch。
为什么比手写验证好?
手写验证:
if (!body.email || typeof body.email !== 'string') {
return error;
}
if (!body.email.includes('@')) {
return error;
}
// 写到天荒地老...Zod:
z.string().email()一行搞定,类型还自动推导。
完整的输入验证方案
验证不只是检查字段类型,还要考虑业务逻辑和边界情况。
1. 验证请求体 (body)
const postSchema = z.object({
title: z.string().min(1).max(100),
content: z.string().max(10000), // 限制长度,防止超大输入
tags: z.array(z.string()).max(10), // 限制数组长度
publishedAt: z.string().datetime().optional(),
});2. 验证查询参数 (query)
// app/api/posts/route.ts
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const querySchema = z.object({
page: z.coerce.number().int().min(1).default(1),
limit: z.coerce.number().int().min(1).max(100).default(20),
sort: z.enum(['asc', 'desc']).default('desc'),
});
const params = querySchema.parse({
page: searchParams.get('page'),
limit: searchParams.get('limit'),
sort: searchParams.get('sort'),
});
// params.page 一定是数字,类型安全
}z.coerce.number() 会自动把字符串转数字,超级方便。
3. 自定义验证规则
const passwordSchema = z.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), 'Must contain uppercase')
.refine((val) => /[a-z]/.test(val), 'Must contain lowercase')
.refine((val) => /[0-9]/.test(val), 'Must contain number');甚至可以异步验证:
const emailSchema = z.string().email().refine(
async (email) => {
const exists = await checkEmailExists(email);
return !exists;
},
'Email already taken'
);4. 错误处理
Zod 的错误信息挺友好,但可以定制:
if (!result.success) {
const errors = result.error.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
}));
return NextResponse.json({ errors }, { status: 400 });
}返回结构化错误,前端容易展示。
其他安全措施
验证之外,还有些重要安全措施:
1. CSRF 保护
Next.js 的 Server Actions 内置了 CSRF 保护。它会比较请求的 Origin 和 Host 头,不匹配就拒绝。
API Routes 需要自己做。如果你用 NextAuth,它会自动处理。手动实现的话,可以用 CSRF token:
// 生成 token 放在 Cookie 里,前端发请求时带上,服务端对比2. Content Security Policy (CSP)
在 next.config.js 配置 CSP,限制页面能加载哪些资源:
{
headers: [
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self'; style-src 'self';",
},
],
}这样就算有 XSS 漏洞,恶意脚本也加载不了。
3. 环境变量安全
Next.js 环境变量分两种:
NEXT_PUBLIC_*开头的会暴露到前端- 不带
NEXT_PUBLIC_的只在服务端可用
千万不要把密钥放在 NEXT_PUBLIC_ 里。我见过有人把 API 密钥写成 NEXT_PUBLIC_API_KEY,直接泄露。
4. SQL 注入防护
用 Prisma、Drizzle 这类 ORM,自动参数化查询,基本不会有问题。
如果非要写原生 SQL,用参数化:
// ❌ 危险
db.query(`SELECT * FROM users WHERE id = ${userId}`);
// ✅ 安全
db.query('SELECT * FROM users WHERE id = ?', [userId]);5. 定期更新依赖
安全漏洞经常出在依赖库里。定期跑:
npm audit
npm update今年2月那个 React 漏洞,只要更新到最新版就修复了。别嫌麻烦,更新一下能省很多麻烦。
完整安全检查清单
说了这么多,给你整理个清单,对照着检查自己的项目:
认证安全清单
- ✅ Token 存储在 HttpOnly Cookie,不用 localStorage
- ✅ Access Token 有效期 ≤ 30 分钟
- ✅ 实现 Refresh Token 机制
- ✅ JWT 密钥至少 32 字符,存在环境变量
- ✅ 启用 Cookie 的
secure和sameSite属性 - ✅ 使用 Middleware 保护敏感 API
CORS 配置清单
- ✅ 不对敏感 API 使用
Access-Control-Allow-Origin: * - ✅ 明确指定允许的域名列表
- ✅ 正确处理 OPTIONS Preflight 请求
- ✅ 需要凭证传递时,设置
Access-Control-Allow-Credentials: true - ✅ 生产环境验证 CORS 配置是否生效
限流配置清单
- ✅ 关键端点(登录、注册、密码重置)启用严格限流
- ✅ 区分认证用户和未认证用户的限流策略
- ✅ 返回 429 状态码和
Retry-After头 - ✅ 使用 Redis 或 Upstash 等持久化存储,避免 Serverless 失效
- ✅ 监控限流触发情况,及时调整阈值
输入验证清单
- ✅ 所有用户输入都经过服务端验证
- ✅ 使用 Zod 或类似工具进行类型安全验证
- ✅ 限制字符串、数组、对象的最大长度
- ✅ 验证数据格式(邮箱、URL、日期等)
- ✅ 返回清晰的验证错误信息
定期审计清单
- ✅ 每月至少运行一次
npm audit,修复高危漏洞 - ✅ 及时更新 Next.js 和 React 到最新稳定版
- ✅ 订阅 Next.js 安全公告,关注新漏洞
- ✅ 审查环境变量,确保没有密钥泄露到前端
- ✅ Code Review 时重点检查认证和权限逻辑
把这个清单打印出来贴在桌上,新项目开始前过一遍,部署前再过一遍。
结论
写了这么多,核心就一句话:API 安全是系统工程,不是一劳永逸的事。
你可能觉得麻烦,认证、CORS、限流、验证,每样都要配置。但等到出了问题,数据泄露、服务器被打垮、收到天价账单,那时候再补救,代价就大了。
我自己的经验:从零开始配置这一套,大概半天到一天时间。但配好了之后,基本就是”复制粘贴改改参数”的事,新项目十几分钟搞定。最重要的是,晚上能睡个安稳觉,不用担心哪天醒来发现被攻击了。
行动建议:
- 立即检查现有项目,对照清单看看缺了什么
- 从最关键的开始,先加认证和限流,再完善其他
- 订阅安全资讯,Next.js 官方博客、GitHub Security Advisories 都看看
- 分享给团队,安全是大家的事,不是一个人的
最后再啰嗦一句:2025年12月那个 CVSS 10.0 的 React 漏洞,影响范围超级大。如果你还没更新,赶紧升级到最新版。安全更新,真的不能拖。
API 安全这条路很长,但每一步都值得。希望这篇文章能帮你少踩些坑,早点把安全防护做到位。
常见问题
Next.js API 应该使用 JWT 还是 Session 认证?
为什么不能把 JWT token 存在 localStorage?
开发环境不报 CORS 错误,生产环境为什么会报?
Serverless 环境下如何实现速率限制?
如何防止 API 被暴力破解密码?
17 分钟阅读 · 发布于: 2026年1月5日 · 修改于: 2026年1月22日
相关文章
Next.js 电商实战:购物车与 Stripe 支付完整实现指南

Next.js 电商实战:购物车与 Stripe 支付完整实现指南
Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战

Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战
Next.js 单元测试实战:Jest + React Testing Library 完整配置指南


评论
使用 GitHub 账号登录后即可评论