如何让Next.js后端仅接收Chrome扩展后台脚本的安全请求
问题:限制Next.js后端仅接收Chrome扩展后台脚本的请求?
我正在开发一个集成Chrome扩展功能的网站,采用Next Auth进行身份验证,需要从扩展端验证用户是否已登录/持有Next Auth令牌。目前的流程是从网站(本地localhost)的Cookie中获取令牌,发送到content.js,再在请求Next.js后端时把令牌放进Authorization请求头里。
Content脚本代码
async function run() { const response = await chrome.runtime.sendMessage({ type: "REQ_COOKIE" }) const cookies = response.cookies as chrome.cookies.Cookie[] console.log(cookies) if (response.cookies.length > 0) { const authCookie = cookies.find( (cookie) => cookie.name == "__Secure-next-auth.session-token" ).value console.log(authCookie) const tokenString = `Bearer ${authCookie}` fetch("http://localhost:3000/api/auth", { headers: { Authorization: tokenString } }) } } run()
后端代码
import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import { getToken } from "next-auth/jwt"; import { env } from "@/env"; import { cookies } from "next/headers"; export async function OPTIONS(req: NextRequest) { return new NextResponse("", { headers: { "Access-Control-Allow-Origin": "*", // replace "*" with your actual origin "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization", }, }); } export async function GET(req: NextRequest) { console.log(req.headers.get("Authorization")); const token = await getToken({ req, secret: env.NEXTAUTH_SECRET }); if (token) { console.log(token); return new NextResponse("Successfull yay", { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization", }, }); } else { return new Response("Not verified", { status: 401, statusText: "Unauthorized", headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization", }, }); } }
请问是否可以配置后端,使其仅接收来自Chrome扩展后台脚本的请求?
解决方案
可以通过两种核心方式实现后端仅接收Chrome扩展的请求,推荐结合使用提升安全性:
方法一:验证Chrome扩展唯一ID
Chrome扩展的后台/内容脚本可以携带自身的唯一ID,后端通过校验这个ID实现访问限制:
- 修改扩展端请求,添加自定义头
在content脚本的fetch请求中,带上扩展ID的自定义头:
fetch("http://localhost:3000/api/auth", { headers: { Authorization: tokenString, "X-Chrome-Extension-Id": chrome.runtime.id // 自动获取当前扩展ID } })
- 后端添加ID校验逻辑
先定义你的扩展ID(可以从Chrome扩展管理页面查看,或通过manifest.json的key字段固定ID),然后在接口中校验:
// 替换为你的Chrome扩展ID const ALLOWED_EXTENSION_ID = "abcdefghijklmnopqrstuvwxyzabcdef"; export async function OPTIONS(req: NextRequest) { const extensionId = req.headers.get("X-Chrome-Extension-Id"); if (extensionId !== ALLOWED_EXTENSION_ID) { return new Response("Forbidden", { status: 403 }); } return new NextResponse("", { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization,X-Chrome-Extension-Id", }, }); } export async function GET(req: NextRequest) { const extensionId = req.headers.get("X-Chrome-Extension-Id"); if (extensionId !== ALLOWED_EXTENSION_ID) { return new Response("Forbidden", { status: 403 }); } // 原有Next Auth令牌校验逻辑 console.log(req.headers.get("Authorization")); const token = await getToken({ req, secret: env.NEXTAUTH_SECRET }); if (token) { console.log(token); return new NextResponse("Successfull yay", { headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization,X-Chrome-Extension-Id", }, }); } else { return new Response("Not verified", { status: 401, statusText: "Unauthorized", headers: { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization,X-Chrome-Extension-Id", }, }); } }
方法二:验证请求Origin为Chrome扩展URL
Chrome扩展的请求Origin格式为chrome-extension://你的扩展ID,后端直接校验这个Origin:
- 后端添加Origin校验逻辑
// 替换为你的Chrome扩展Origin const ALLOWED_EXTENSION_ORIGIN = "chrome-extension://abcdefghijklmnopqrstuvwxyzabcdef"; export async function OPTIONS(req: NextRequest) { const origin = req.headers.get("Origin"); if (origin !== ALLOWED_EXTENSION_ORIGIN) { return new Response("Forbidden", { status: 403 }); } return new NextResponse("", { headers: { "Access-Control-Allow-Origin": ALLOWED_EXTENSION_ORIGIN, // 不要用*,和Credentials冲突 "Access-Control-Allow-Credentials": "true", "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Headers": "Authorization", }, }); } export async function GET(req: NextRequest) { const origin = req.headers.get("Origin"); if (origin !== ALLOWED_EXTENSION_ORIGIN) { return new Response("Forbidden", { status: 403 }); } // 原有Next Auth令牌校验逻辑... }
注意事项
- 固定扩展ID:可以通过在manifest.json中添加
key字段(从已打包扩展导出),避免开发和生产环境ID不一致。 - CORS规则:不要同时设置
Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true,这不符合CORS规范,验证Origin后直接指定允许的Origin值即可。 - 双重验证:建议同时校验扩展ID和Origin,进一步提升接口安全性。
内容的提问来源于stack exchange,提问作者DevEnma
相关产品推荐
相关产品推荐

