让Angular与严格的内容安全策略(CSP)配合使用

44
我无法使基本的Angular2(最终版)应用程序与以下限制性CSP一起工作。
default-src 'none';
script-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data:;
connect-src 'self'

lang.js中有一个unsafe-eval错误,在zone.js中有两个。您能提供解决方案吗?

使用Angular CLI重现步骤

我创建了一个GitHub存储库。您也可以按照以下说明进行操作。

使用最新的Angular CLI和Webpack 6.0.8以及以下说明创建的新应用程序。

ng new csp-test

请在 index.html 中插入定义以下限制性内容安全策略的元标签。
<meta 
  http-equiv="Content-Security-Policy" 
  content="default-src 'none';script-src 'self';style-src 'self';font-src 'self';img-src 'self' data:;connect-src 'self'">

然后启动应用程序。
ng serve

访问 http://localhost:4200/ 时,页面因为脚本被 CSP 阻止而无法加载。

错误

Error in Chrome

lang.js

lang.js:335 Uncaught EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".

使用源代码。
335: return new (Function.bind.apply(Function, [void 0].concat(fnArgNames.concat(fnBody))))().apply(void 0, fnArgValues);

zone.js

zone.js:344 Unhandled Promise rejection: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".
 ; Zone: <root> ; Task: Promise.then ; Value: EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".

zone.js:346 Error: Uncaught (in promise): EvalError: Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self'".(…)

使用源代码。
343: if (rejection) {
344:     console.error('Unhandled Promise rejection:', rejection instanceof Error ? rejection.message : rejection, '; Zone:', e.zone.name, '; Task:', e.task && e.task.source, '; Value:', rejection, rejection instanceof Error ? rejection.stack : undefined);
345: }
346: console.error(e);

你是否正在模板中使用生成的值?例如像 <img [src]="function(input)"/><object data="{{fieldValue}}"></object> 这样的内容?Angular 为了防止跨站脚本攻击,不允许使用不安全的值。 - Scrambo
5个回答

23

针对 @angular/cli>=8.2 进行编辑的答案

这个 Github 线程中,我们可以使用 angular.json 中的 index 属性来控制生成应用程序的 HTML index:

build: {
  ...
  "configurations": {
    "production": {
      "index": {
        "input": "src/index.production.html",
         "output": "index.html"
       },
      ...
    }
  }
}

原始回答

我已经找到了一种方法,在生产环境上实现严格的CSP,同时还能够在开发中使用JTI编译器。

  • 添加第二个文件:index.production.htmlsrc 文件夹中。
  • index.html的内容复制到该文件中,并添加严格的CSP头。
<meta http-equiv="Content-Security-Policy" 
content="default-src 'none';
  frame-src 'self';
  script-src 'self';
  style-src 'self' 'unsafe-inline';
  font-src 'self';
  img-src 'self' data:;
  connect-src 'self'">
  • 然后,在你的angular.json文件中添加以下内容:
build: {
  ...
  "configurations": {
    "production": {
      "fileReplacements": [
        {
          "replace": "src/index.html",
          "with": "src/index.production.html"
        }
      ],
      ...
    }
  }
}

这样做可以确保在运行生产版本时,它将使用限制性CSP的 index.production.html 文件,而在本地运行时,则可以使用JTI编译器。


4
这是一个很好的解决方案,但现在不起作用了。 fileReplacements 属性无法考虑非捆绑文件,比如 index.html。已更新的解决方案请参见 https://github.com/angular/angular-cli/issues/14599#issuecomment-527131237 - Blockost
这个与上面评论中更新的解决方案结合起来对我有些作用。我能够将CSP元标记添加到我的生产构建中,但securityheaders.com仍然说我缺少它们,而且我的woff2文件中有许多(不是全部)现在被拒绝了? - Eric Soyke
1
我不得不将'unsafe-inline'添加到script-src中才能使其工作(Angular 12) - Christophe Le Besnerais
'unsafe-inline'添加到'script-src'不会破坏'Content-Security-Policy'的目标。Angular无法编译并将所有脚本哈希添加到CSP头吗? - IsaacCampos

5
使用预编译可以解决这个问题。以下命令可用于构建与限制性CSP一起使用的应用程序。
ng build --prod

要在本地测试它,您可以使用本地功能

ng serve --prod

8
当你运行ng serve命令时:这是一个简单的服务器,用于本地测试或调试Angular应用程序。但它未经过安全问题的审查。不要将其用于生产环境! - chris31389
1
即使使用ng build --prod生成AOT构建,也无法解决CSP问题。 - saikarthik parachi

3

第一个链接也使用了 zone.js,这将触发相同的错误。在 GitHub 问题中,最后一条评论说它并非适用于所有应用程序,只适用于简单的应用程序。 - Nicolas Henneaux
离线模板编译器是相当新的。如果它不能正常工作,请使用最小的仓库项目报告错误。 - Günter Zöchbauer

0

我在使用部署在 WildFly 服务器 上的 Angular 9 时遇到了这个问题。问题出在 WildFly 服务器 的配置上,具体是在 standalone.xml 中设置的 response-header,而不是我之前认为的在 index.html 中。

standalone.xml:

<filters>
    <response-header name="server-header" header-name="Server" header-value="JBoss-EAP/7"/>
    <response-header name="x-powered-by-header" header-name="X-Powered-By" header-value="Undertow/1"/>
    >>> Here >>> <response-header name="Content-Security-Policy" header-name="Content-Security-Policy" header-value="default-src 'self'; style-src 'self' 'unsafe-inline'"/>
    <response-header name="x-frame-options" header-name="X-Frame-Options" header-value="SAMEORIGIN"/>
    <response-header name="x-xss-protection" header-name="X-XSS-Protection" header-value="1; mode=block"/>
    <response-header name="x-content-type-options" header-name="X-Content-Type-Options" header-value="nosniff"/>
    <response-header name="strict-transport-security" header-name="Strict-Transport-Security" header-value="max-age=31536000; includeSubDomains;"/>
    <response-header name="my-custom-header" header-name="my-custom-header" header-value="my-custom-value"/>
</filters>

注意:重新启动WildFly服务器

./jboss-cli.sh --connect command=:reload

jboss-cli位于WildFly服务器bin文件夹中:


0

注意,我从Angular 10文档中获取了这个信息,链接在这里:https://angular.io/guide/security

当我在服务器响应中添加了这个头部时,我解决了这个问题。

Content-Security-Policy: trusted-types angular; require-trusted-types-for 'script';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接