You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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&amp;title=Explore%20-%20Factiii&amp;referrer=&amp;muid=52c1d508-2a86-4261-9596-931ef99d8ff21fd5ef&amp;sid=f19e156f-257a-4e0b-accf-26ae0b7f8519fed5f9&amp;version=6&amp;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&amp;title=Explore%20-%20Factiii&amp;referrer=&amp;muid=52c1d508-2a86-4261-9596-931ef99d8ff21fd5ef&amp;sid=f19e156f-257a-4e0b-accf-26ae0b7f8519fed5f9&amp;version=6&amp;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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:35:34