NextJS使用loadStripe在FireFox触发CSP错误,求正确CSP配置方案
问题
我开发了一个Next.js应用,通过@stripe/stripe-js的loadStripe对接Stripe处理交易,但在Firefox中添加代码后出现多个CSP警告,同时还有Cookie警告。代码能正常运行,但我想解决这些警告。之前尝试在_document.tsx中添加meta标签,结果问题更严重,请问如何正确配置CSP消除这些警告?
标准loadStripe实现代码
import { loadStripe } from "@stripe/stripe-js"; const PurchaseCoinsDropdownModal = ({ ...props }: Props) => { // 调用此方法会触发警告 const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUB_KEY as string); return ( <Elements options={{ clientSecret, appearance: { theme: document.body.classList[0] === "dark" ? "night" : "stripe" } }} stripe={stripePromise} > <CheckoutForm total={usdFix(selectedCoin.price)} closeHandler={props.closeHandler} /> </Elements> ); };
警告信息(中文翻译)
内容安全策略:页面设置阻止了内联资源的加载(“script-src”)。sandbox eval code:544:346 内容安全策略:页面设置监测到内联资源的加载(“script-src”)。正在发送CSP报告。sandbox eval code:544:346 已为“https://js.stripe.com/v3/m-outer-3437aaddcdf6922d623e172c2d6f9278.html#url=http%3A%2F%2Flocalhost%3A3000%2F&title=Explore%20-%20Factiii&referrer=&muid=52c1d508-2a86-4261-9596-931ef99d8ff21fd5ef&sid=f19e156f-257a-4e0b-accf-26ae0b7f8519fed5f9&version=6&preview=false”提供分区Cookie或存储访问权限,因为它在第三方上下文中加载且已启用动态状态分区。 内容安全策略:页面设置阻止了内联资源的加载(“script-src”)。sandbox eval code:544:346 内容安全策略:页面设置监测到内联资源的加载(“script-src”)。正在发送CSP报告。sandbox eval code:544:346 已为“https://m.stripe.network/inner.html#url=http%3A%2F%2Flocalhost%3A3000%2F&title=Explore%20-%20Factiii&referrer=&muid=52c1d508-2a86-4261-9596-931ef99d8ff21fd5ef&sid=f19e156f-257a-4e0b-accf-26ae0b7f8519fed5f9&version=6&preview=false”提供分区Cookie或存储访问权限,因为它在第三方上下文中加载且已启用动态状态分区。 资源被OpaqueResponseBlocking阻止,请查看浏览器控制台获取详细信息。4 csp-report 内容安全策略:页面设置阻止了内联资源的加载(“script-src”)。
解决方案
1. 放弃meta标签,改用HTTP头配置CSP
meta标签配置CSP存在局限性(无法设置frame-ancestors、sandbox等指令),直接通过Next.js的服务端逻辑设置HTTP头更可靠。
Pages Router 配置(修改_document.tsx)
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'; class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const initialProps = await Document.getInitialProps(ctx); // 设置CSP头 ctx.res?.setHeader( 'Content-Security-Policy', [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://m.stripe.network", "frame-src 'self' https://js.stripe.com https://m.stripe.network", "style-src 'self' 'unsafe-inline' https://js.stripe.com", "img-src 'self' data: https://*.stripe.com", "connect-src 'self' https://api.stripe.com https://m.stripe.network", "font-src 'self' https://js.stripe.com" ].join('; ') ); return initialProps; } render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); } } export default MyDocument;
App Router 配置(创建middleware.ts)
import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { const response = NextResponse.next(); response.headers.set( 'Content-Security-Policy', [ "default-src 'self'", "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://js.stripe.com https://m.stripe.network", "frame-src 'self' https://js.stripe.com https://m.stripe.network", "style-src 'self' 'unsafe-inline' https://js.stripe.com", "img-src 'self' data: https://*.stripe.com", "connect-src 'self' https://api.stripe.com https://m.stripe.network", "font-src 'self' https://js.stripe.com" ].join('; ') ); return response; } // 匹配所有路由 export const config = { matcher: '/:path*', };
2. 处理Cookie分区警告
Firefox的这个提示是浏览器按规范处理第三方Cookie的正常行为,Stripe SDK已经自动适配了Cookie分区机制,无需额外修改代码。如果不想看到提示,只需确认功能正常即可,不影响交易流程。
3. 优化loadStripe调用逻辑
把loadStripe移到组件外部,避免每次渲染重复初始化,确保只加载一次:
import { loadStripe } from "@stripe/stripe-js"; // 全局初始化一次,避免重复加载 const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUB_KEY as string); const PurchaseCoinsDropdownModal = ({ ...props }: Props) => { return ( <Elements options={{ clientSecret, appearance: { theme: document.body.classList[0] === "dark" ? "night" : "stripe" } }} stripe={stripePromise} > <CheckoutForm total={usdFix(selectedCoin.price)} closeHandler={props.closeHandler} /> </Elements> ); };
4. 生产环境CSP强化
生产环境可以替换'unsafe-inline'为nonce或哈希值,提升安全性:
// Pages Router示例,生成随机nonce import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'; import crypto from 'crypto'; class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { const initialProps = await Document.getInitialProps(ctx); const nonce = crypto.randomBytes(16).toString('hex'); ctx.res?.setHeader( 'Content-Security-Policy', [ "default-src 'self'", `script-src 'self' 'nonce-${nonce}' 'unsafe-eval' https://js.stripe.com https://m.stripe.network`, "frame-src 'self' https://js.stripe.com https://m.stripe.network", "style-src 'self' 'unsafe-inline' https://js.stripe.com", "img-src 'self' data: https://*.stripe.com", "connect-src 'self' https://api.stripe.com https://m.stripe.network", "font-src 'self' https://js.stripe.com" ].join('; ') ); return { ...initialProps, nonce }; } render() { const { nonce } = this.props; return ( <Html> <Head nonce={nonce} /> <body> <Main /> <NextScript nonce={nonce} /> </body> </Html> ); } } export default MyDocument;
内容的提问来源于stack exchange,提问作者rockets4all
相关产品推荐
相关产品推荐

