Scala Play框架CSP配置引发脚本加载拒绝错误原因排查
问题背景
项目基于Scala Play Framework 2.8.19,配置了Content Security Policy(CSP)过滤器,具体配置如下:
play.filters.csp { directives { default-src = "'self'" img-src = "'self' *.fbcdn.net *.twimg.com *.googleusercontent.com *.xingassets.com vk.com *.yimg.com secure.gravatar.com chart.googleapis.com *.fbsbx.com api.qrserver.com" style-src = "'self' 'unsafe-inline' cdnjs.cloudflare.com maxcdn.bootstrapcdn.com cdn.jsdelivr.net fonts.googleapis.com" font-src = "'self' fonts.gstatic.com fonts.googleapis.com cdnjs.cloudflare.com" script-src = ${play.filters.csp.directives.script-src} "'self' cdnjs.cloudflare.com" connect-src = "'self' twitter.com *.xing.com" } }
禁用CSP过滤器时脚本可正常加载,启用后Chrome浏览器抛出以下错误:
Refused to load the script 'http://localhost:9000/static/js/main.291e8d2b.js' because it violates the following Content Security Policy directive: "script-src 'nonce-m7r6oxzmy1TvABLGWrCMAA==' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http: 'self' cdnjs.cloudflare.com". Note that 'strict-dynamic' is present, so host-based allowlisting is disabled. Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
最初的script-src配置为:
script-src = ${play.filters.csp.directives.script-src} "cdnjs.cloudflare.com"
后续尝试添加了'self',但仍无法理解错误产生的原因。
核心原因
问题出在**strict-dynamic指令的特性**:当该指令存在时,CSP会直接禁用所有基于主机的白名单规则,包括你配置的'self'和cdnjs.cloudflare.com都会失效。
Play Framework 2.8.x的CSP默认配置中,${play.filters.csp.directives.script-src}会自动注入strict-dynamic、nonce(随机生成的一次性令牌)以及unsafe-inline/unsafe-eval。此时CSP的规则逻辑切换为信任链模式:只有通过已被信任的脚本加载的脚本才会被允许,直接配置的主机域名白名单不再生效。
你的本地脚本http://localhost:9000/static/js/main.291e8d2b.js属于直接加载的顶级脚本,它既没有携带与CSP响应头中匹配的nonce值,也不是由已信任脚本动态加载的,因此被CSP拦截拒绝。
解决方案
根据实际需求,可选择以下两种方案:
方案1:移除strict-dynamic,启用主机白名单
修改script-src配置,不再继承默认的包含strict-dynamic的规则,直接指定允许的主机:
script-src = "'self' cdnjs.cloudflare.com"
这样配置后,主机白名单规则会正常生效,允许加载来自自身域名和cdnjs.cloudflare.com的脚本。
方案2:保留strict-dynamic,为顶级脚本添加nonce
如果需要维持strict-dynamic的信任链模式,需确保所有直接加载的顶级脚本都携带Play自动生成的nonce值:
在模板中加载脚本时,使用Play提供的CSPNonce助手注入nonce:
<script src="@routes.Assets.versioned("static/js/main.291e8d2b.js")" nonce="@CSPNonce.get"></script>
添加nonce后,该脚本会被CSP识别为可信脚本,后续由它动态加载的其他脚本也会自动被信任。
内容的提问来源于stack exchange,提问作者Chris Carrington

