如何防止Next.js API接口被未授权访问?
解决办法:限制Next.js API仅允许自有域名访问
针对你的需求——让访客无需登录就能查看作品集,但禁止外部网站直接调用API,以下是几个可行方案:
方案1:校验请求的Referer/Origin头
浏览器发起请求时会自动带上Referer(来源页面)或Origin(来源域名)头,我们可以在API路由里检查这些头是否属于你的域名,以此判断请求是否合法。
修改app/api/portfolio/route.js:
import { headers, NextResponse } from 'next/server'; import { connectDB, getPortfolioItems } from './path-to-your-utils'; // 替换成你的工具文件路径 export const GET = async (request) => { // 允许的域名列表,包含生产环境和本地开发环境 const allowedDomains = [ 'https://codingsamadhan.com', 'http://codingsamadhan.com', 'http://localhost:3000' ]; const origin = headers().get('origin'); const referer = headers().get('referer'); // 检查Origin或Referer是否匹配允许的域名 const isAllowed = allowedDomains.some(domain => { if (origin) return origin.startsWith(domain); if (referer) return referer.startsWith(domain); return false; }); if (!isAllowed) { return NextResponse.json({ message: "Unauthorized Access!" }, { status: 403 }); } await connectDB(); const portfolioItems = await getPortfolioItems(request); return NextResponse.json(portfolioItems); };
注意点
- 同域请求可能没有
Origin头,所以要结合Referer一起校验 - 虽然这些头可以被伪造,但足以阻止绝大多数普通的外部API调用,完全符合你的需求
- 本地开发环境必须加入允许列表,不然开发时会被拦截
方案2:用Next.js Middleware统一拦截
如果你有多个API路由需要保护,用Middleware可以一次性处理所有API请求,不用在每个路由里重复写校验代码。
在项目根目录创建middleware.js:
import { NextResponse } from 'next/server'; export function middleware(request) { const allowedDomains = [ 'https://codingsamadhan.com', 'http://codingsamadhan.com', 'http://localhost:3000' ]; const origin = request.headers.get('origin'); const referer = request.headers.get('referer'); const isAllowed = allowedDomains.some(domain => { if (origin) return origin.startsWith(domain); if (referer) return referer.startsWith(domain); return false; }); // 只拦截/api开头的请求 if (request.nextUrl.pathname.startsWith('/api/') && !isAllowed) { return NextResponse.json({ message: "Unauthorized Access!" }, { status: 403 }); } return NextResponse.next(); } // 指定要拦截的路径规则 export const config = { matcher: '/api/:path*', };
关于next-auth/JWT无需登录的方案
如果你一定要用next-auth,其实可以配置匿名会话,让未登录用户自动获得一个合法会话,然后在API里校验这个会话:
- 安装next-auth:
npm install next-auth - 创建
app/api/auth/[...nextauth]/route.js:
import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export const authOptions = { providers: [ CredentialsProvider({ name: "Anonymous", credentials: {}, // 不需要用户输入凭证 async authorize() { // 返回一个匿名用户对象,用于生成会话 return { id: "anonymous", name: "Anonymous Visitor" }; }, }), ], session: { strategy: "jwt" }, // 用JWT存储会话 secret: process.env.NEXTAUTH_SECRET, // 从环境变量获取密钥 }; const handler = NextAuth(authOptions); export { handler as GET, handler as POST };
- 修改API路由校验会话:
import { getServerSession } from "next-auth/next"; import { authOptions } from "../auth/[...nextauth]/route"; import { connectDB, getPortfolioItems } from './path-to-your-utils'; export const GET = async (request) => { const session = await getServerSession(authOptions); // 只要存在会话(不管是否是匿名)就允许访问 if (!session) { return NextResponse.json({ message: "Unauthorized Access!" }, { status: 403 }); } await connectDB(); const portfolioItems = await getPortfolioItems(request); return NextResponse.json(portfolioItems); };
- 在前端页面自动初始化匿名会话:
import { signIn } from "next-auth/react"; import { useEffect } from "react"; export default function PortfolioPage() { useEffect(() => { // 页面加载时自动登录匿名用户,无需跳转 signIn("credentials", { redirect: false }); }, []); // 页面内容... }
不过这个方案相对复杂,其实前面的Referer/Origin校验已经完全能满足你的需求,而且更轻量,没必要多此一举。
为什么X-Api-Key方案没用
前端代码里的任何密钥都会在浏览器的网络面板里暴露,所以后端不能依赖前端传过来的密钥做鉴权——这种方式只适合服务器之间的调用,不适合浏览器发起的请求,你的判断是对的。
内容的提问来源于stack exchange,提问作者Md Injamul
相关产品推荐
相关产品推荐

