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

Remix集成Google登录遇CSP错误:如何加载GSI脚本?

解决Remix集成Google登录时的Content Security Policy脚本加载错误

组件代码

import {useMatches} from '@remix-run/react';
import {useRef, useEffect} from 'react';
import {multipass} from '~/lib/multipass/multipass';

interface GoogleJwTResponseType {
  credential: string;
}

declare global {
  var google: {
    accounts: {
      id: {
        initialize: (options: any) => void;
        renderButton: (element: HTMLDivElement | null, options: any) => void;
      };
    };
  };
}

/*
  Google Sign in component.
  @see: https://developers.google.com/identity/gsi/web/guides/display-button
*/
export function GoogleLoginButton() {
  const [root] = useMatches();
  const env = root?.data?.env;
  const init = useRef(false);
  const buttonRef = useRef<HTMLDivElement>(null);

  async function handleJwtResponse(response: GoogleJwTResponseType) {
    console.log(response)
    // authenticate google customer info via multipass
    await multipass({
      token: response.credential,
      provider: 'google',
      redirect: true, // will redirect to
    });
  }

  useEffect(() => {
    // if (typeof window?.google !== 'undefined' || init.current) return;

    init.current = true;

    const script = document.createElement('script');
    script.src = 'https://accounts.google.com/gsi/client';
    script.async = true;
    script.defer = true;
    script.onload = () => {
      google.accounts.id.initialize({
        client_id: 'xxx', // 原代码缺少闭合引号,已修正
        callback: handleJwtResponse,
      });
      // @see: https://developers.google.com/identity/gsi/web/reference/js-reference#GsiButtonConfiguration
      google.accounts.id.renderButton(
        buttonRef.current,
        {
          theme: 'outline',
          text: 'Login in with Google',
          size: 'large',
          type: 'standard',
          autoPrompt: false,
        }, // customization attributes
      );
    };

    console.log(document.body);

    document.body.appendChild(script);
  }, []);

  return (
    <div style={{width: '220px', height: '44px', overflow: 'hidden'}}> {/* 原代码overflow拼写错误,已修正 */}
      <div ref={buttonRef}>Loading...</div>
    </div>
  );
}

问题

在Remix应用中集成Google登录时,触发了CSP错误:

refused to load the script 'https://accounts.google.com/gsi/client' because it violates the following Content Security Policy directive: "default-src 'self' 'nonce-xxx' https://cdn.shopify.com https://shopify.com". Note that 'script-src-elem' was not explicitly set, so 'default-src' is used as a fallback.

该如何通过设置Meta标签或修改脚本解决这个问题?

解决方案

1. 调整Content Security Policy(核心解决方法)

当前CSP的default-src规则未包含Google脚本的域名,导致加载被拦截。必须更新CSP,允许从accounts.google.com加载脚本。

方式一:在Remix根路由中设置Meta标签

在root.tsx的meta函数里添加CSP规则,明确指定script-src-elem:

export function meta() {
  return [
    {
      httpEquiv: "Content-Security-Policy",
      content: `default-src 'self' 'nonce-xxx' https://cdn.shopify.com https://shopify.com; script-src-elem 'self' 'nonce-xxx' https://accounts.google.com;`,
    },
  ];
}

如果应用使用了nonce,还要给动态创建的脚本加上nonce属性:

// 在组件中获取根路由传递的nonce
const nonce = root?.data?.nonce;

// 创建脚本时添加nonce
const script = document.createElement('script');
script.src = 'https://accounts.google.com/gsi/client';
script.async = true;
script.defer = true;
if (nonce) {
  script.nonce = nonce;
}

方式二:服务器端配置CSP响应头

如果用自定义服务器(比如Express)部署Remix,直接在服务器端设置响应头更安全:

// Express示例代码
app.use((req, res, next) => {
  res.setHeader(
    "Content-Security-Policy",
    `default-src 'self' 'nonce-${req.nonce}' https://cdn.shopify.com https://shopify.com; script-src-elem 'self' 'nonce-${req.nonce}' https://accounts.google.com;`
  );
  next();
});

2. 额外修正

原代码里有两处小问题,建议修复:

  • client_id: 'xxx, 缺少闭合单引号
  • overflow: 'hidde' 拼写错误,应为overflow: 'hidden'

内容的提问来源于stack exchange,提问作者dev vikas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:07:26