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
相关产品推荐
相关产品推荐

