内容安全策略(CSP)通过HTTP响应头或HTML中的<meta>标签告诉浏览器哪些资源可以加载、哪些脚本可以执行。React应用通常由打包工具生成外部JavaScript文件,理论上与CSP的script-src 'self'兼容,但实际部署时却经常出现控制台报错,原因是构建产物中可能包含内联脚本、样式注入以及第三方服务的动态加载。要解决这些问题,需要从构建配置和CSP指令两个维度同时调整。

CSP基本指令与React构建产物的关系
CSP最常见的限制来自script-src指令。如果只设置script-src 'self',浏览器会拒绝所有内联脚本以及来自其他域名的脚本。React官方脚手架默认在生产构建中会把webpack runtime代码内联到index.html中,以减少请求数量。这个内联runtime脚本没有nonce或hash,因此会被CSP直接拦截,导致页面空白。解决思路是关闭内联runtime,或者给内联脚本加上nonce属性。在生产环境更推荐前者,因为nonce需要动态生成且不能缓存,实现成本较高。
除了脚本,style-src同样重要。React开发中使用CSS模块、styled-components或Emotion等方案时,样式可能通过JavaScript动态插入到<style>标签中。这些动态样式会被CSP视为内联样式,必须允许unsafe-inline或者为每个style标签添加nonce。对于styled-components等CSS-in-JS库,官方推荐在服务端渲染时配合nonce,纯客户端渲染下则可能需要style-src 'unsafe-inline',但这样会削弱CSP对样式的保护。折中方案是提取静态CSS文件,减少运行时注入。
img-src和connect-src分别控制图片加载和接口请求。React应用中通常会加载用户头像、第三方图片、调用API接口,如果CSP中没有包含对应域名,这些资源会被静默拦截或报错。因此配置CSP前需要梳理应用依赖的所有资源类型。下面是一个基础的CSP响应头示例,适合通过后端或服务器配置下发。
Content-Security-Policy: script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'
不同构建工具下的配置方案
Create React App项目可以通过修改.env文件设置INLINE_RUNTIME_CHUNK=false,这样构建时就不会把runtime内联到HTML中。设置后重新构建,index.html中不再包含内联脚本,script-src 'self'即可生效。如果项目使用了CSS-in-JS,还需要处理style-src,建议使用静态CSS或配置nonce。以下配置适用于CRA 4及以上版本。
INLINE_RUNTIME_CHUNK=false
Vite默认构建不会注入内联运行时,但会在开发模式下通过WebSocket和esbuild动态加载模块。开发环境可以适当放宽CSP,例如允许connect-src ws:和script-src 'unsafe-inline',生产环境则保持'self'。如果使用Vite的插件生成PWA或预加载,需要检查生成的index.html是否包含内联代码。自定义Webpack项目需要确认optimization.runtimeChunk是否生成单独文件,并避免使用HtmlWebpackPlugin的inline选项。
无论使用哪种构建工具,都可以通过浏览器开发者工具的网络面板查看加载的脚本和样式来源,从而确定需要加入CSP白名单的域名。对于第三方脚本如统计、错误监控,建议通过CSP的nonce或hash精确放行,而不是直接加入域白名单,因为域白名单可能被绕过。
常见CSP报错与解决方法
控制台常见的报错信息包括“Refused to execute inline script because it violates the following Content Security Policy directive”、“Refused to apply inline style”以及“Refused to connect to ...”。这些错误分别对应script-src、style-src和connect-src的限制。遇到时不要马上添加unsafe-inline或unsafe-eval,而应定位具体是哪个脚本或样式被拦截。可以通过浏览器控制台的报错详情查看被拦截资源的URL或代码片段。
如果是内联脚本被拦截,检查是不是构建时内联了runtime或第三方SDK注入的脚本。对于必须内联的脚本,可以在服务端渲染时为<script>标签添加nonce属性,并在CSP头中加入nonce-xxxx。注意nonce值必须在每次请求时随机生成,否则攻击者可以预测。若无法动态生成nonce,可以使用脚本内容的哈希值,CSP支持script-src 'sha256-...',不过哈希方式要求内容完全一致,脚本微小变化都会导致哈希失效。
style-src报错多来自动态样式。如果使用styled-components,可在创建GlobalStyle时传入nonce;如果是CSS Modules,打包后应生成独立CSS文件,不会被拦截。如果必须使用内联样式,可以设置style-src 'unsafe-inline',但建议缩小范围,仅对开发环境或特定路由放开。此外还可以使用report-uri或report-to指令收集CSP违规报告,帮助定位遗漏的资源。下面给出一个带报告指令的CSP配置。
Content-Security-Policy: script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; report-uri /csp-report-endpoint
生产环境CSP实战建议
生产环境的CSP应当尽量收紧,script-src建议只包含'self'和必要的第三方域,不要使用unsafe-eval和unsafe-inline。React本身不需要eval,除非代码中使用了Webpack的devtool eval模式,生产构建应避免。style-src可以设置为'self',如果使用CSS-in-JS库,需要评估是否必须允许unsafe-inline,或改为构建期提取样式。img-src建议包含'self' data: blob:以及允许的第三方图片域名。connect-src列出所有API域名,不要使用通配符。
在滚动更新CSP策略时,可以先用Content-Security-Policy-Report-Only模式只报告不拦截,观察一段时间后再正式启用。另外注意CSP与跨域资源共享(CORS)的区别,CSP主要控制前端加载行为,CORS控制服务端是否允许跨域请求,两者不能互相替代。测试时可以使用CSP Evaluator等工具检查策略是否存在绕过风险。
CSP配置不是一劳永逸,随着React应用依赖变化需要定期审查。通过合理调整构建产物、使用nonce或hash、精准设置各资源指令,可以让CSP真正发挥防御XSS的作用,同时不影响用户体验。