Angular 中动态编译的延迟加载动态路由导致“不安全评估”错误
Posted
技术标签:
【中文标题】Angular 中动态编译的延迟加载动态路由导致“不安全评估”错误【英文标题】:Dynamically compiled lazy loaded dynamic routes in Angular causing 'unsafe-eval' error 【发布时间】:2021-02-21 01:58:51 【问题描述】:在应用内容安全策略后,在 Angular 应用程序的 index.html 文件中,应用程序给出了“unsafe-eval”控制台错误,如下所示 -
core.js:4442 ERROR 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: "default-src 'self'".
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: "default-src 'self'".
at new Function (<anonymous>)
at JitEvaluator.evaluateCode (compiler.js:6740)
at JitEvaluator.evaluateStatements (compiler.js:6714)
at CompilerFacadeImpl.jitExpression (compiler.js:19300)
at CompilerFacadeImpl.compileNgModule (compiler.js:19238)
at Function.get (core.js:25864)
at getNgModuleDef (core.js:1853)
at new NgModuleFactory$1 (core.js:24270)
at Compiler_compileModuleSync__POST_R3__ (core.js:27085)
at Compiler_compileModuleAsync__POST_R3__ [as compileModuleAsync] (core.js:27090)
at resolvePromise (zone-evergreen.js:798)
at resolvePromise (zone-evergreen.js:750)
at zone-evergreen.js:860
at ZoneDelegate.invokeTask (zone-evergreen.js:399)
at Object.onInvokeTask (core.js:27483)
at ZoneDelegate.invokeTask (zone-evergreen.js:398)
at Zone.runTask (zone-evergreen.js:167)
at drainMicroTaskQueue (zone-evergreen.js:569)
当我尝试动态构建模块时,使用 Compiler 类中的 compileModuleAsync() 方法会导致此错误。
如果我不使用内容安全策略,那么应用程序可以正常工作并且不会出现此类控制台错误。以下是政策详情 -
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
根据调用堆栈的观察,Angular Framework 的以下函数部分使用new Function()
表达式并导致安全问题 -
/**
* Evaluate a piece of JIT generated code.
* @param sourceUrl The URL of this generated code.
* @param ctx A context object that contains an AST of the code to be evaluated.
* @param vars A map containing the names and values of variables that the evaluated code might
* reference.
* @param createSourceMap If true then create a source-map for the generated code and include it
* inline as a source-map comment.
* @returns The result of evaluating the code.
*/
evaluateCode(sourceUrl, ctx, vars, createSourceMap)
let fnBody = `"use strict";$ctx.toSource()\n//# sourceURL=$sourceUrl`;
const fnArgNames = [];
const fnArgValues = [];
for (const argName in vars)
fnArgValues.push(vars[argName]);
fnArgNames.push(argName);
if (createSourceMap)
// using `new Function(...)` generates a header, 1 line of no arguments, 2 lines otherwise
// E.g. ```
// function anonymous(a,b,c
// /**/) ... ```
// We don't want to hard code this fact, so we auto detect it via an empty function first.
const emptyFn = new Function(...fnArgNames.concat('return null;')).toString();
const headerLines = emptyFn.slice(0, emptyFn.indexOf('return null;')).split('\n').length - 1;
fnBody += `\n$ctx.toSourceMapGenerator(sourceUrl, headerLines).toJsComment()`;
const fn = new Function(...fnArgNames.concat(fnBody));
return this.executeFunction(fn, fnArgValues);
这是我试图构建在 loadChildren 中编写的配置的 routes.json -
path: '',
componentName: 'dummy',
children: [
path: '',
pathMatch: 'full',
redirectTo: 'set-focus-action',
,
path: 'set-focus-action',
loadChildren:
routes: [
path: '',
componentName: 'dynamicType1',
,
],
,
,
],
下面是构建模块的代码-
private featureModule(loadChildren: string): Observable<Type<any>>
return this.getRoutes(loadChildren).pipe(
switchMap((routesConfig) =>
const module = NgModule(this.createFeatureModule(routesConfig))(
class
);
return from(this.compiler.compileModuleAsync(module));
),
map((m) =>
return m.moduleType;
)
);
另外,我正在为这个编译器使用 JitCompilerFactory -
provide: COMPILER_OPTIONS, useValue: , multi: true ,
provide: CompilerFactory,
useClass: JitCompilerFactory,
deps: [COMPILER_OPTIONS],
,
provide: Compiler,
useFactory: createCompiler,
deps: [CompilerFactory],
如果有任何其他细节,请告诉我。任何建议都会很有帮助。
以下是 stackblitz 的链接,该问题可重现 https://stackblitz.com/github/HimanshuGoel/unsafe-eval-issue?file=src%2Findex.html
如果我删除此 CSP,它会正确渲染 -
【问题讨论】:
你的问题太长了......你如何在stackblitz.com/fork/angular-ivy上构建你的问题? 感谢@TheFabio 的回复。我已经在下面的链接中创建了我的问题的片段 - stackblitz.com/github/HimanshuGoel/unsafe-eval-issue 如果您转到 index.html 文件,您会注意到那里的 CSP 策略,如果您尝试删除以评论该 CSP 策略,它将正确呈现。 这就是我所说的冗长......你可能想从这个堆栈闪电战中删除 90% 的代码,没有多少人会热衷于查看这么多文件 当然@TheFabio,我已经删除了额外的代码和文件。现在,它是在渲染动态延迟加载路由时重现此问题的最小版本。请再看一遍 - stackblitz.com/github/HimanshuGoel/… 【参考方案1】:不幸的是,没有直接的解决方法。 Angular JIT编译器需要使用new Function
,生成动态模块需要JIT编译器。
所以你有两个选择,添加unsafe-eval
作为内容源:
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-eval';" />
或者返回绘图板重新评估您对动态模块的需求。一般来说,建议不要使用 JIT,因为它带来的大小增加和速度降低。例如,最新的 Angular 版本默认使用 AOT,即使在 ng serve
模式下也是如此。
【讨论】:
【参考方案2】:这个问题的原因似乎是current Angular deficiency
这是问题的minimalistic reproduction。我们只需将 CSP 元标记添加到标准 stackblitz 应用程序的页头:
<meta http-equiv="Content-Security-Policy" content="default-src 'self';" />
Webpack configuration 将提供对 CSP 的支持
webpack 能够将
nonce
添加到它加载的所有脚本中
但是,这是not currently supported by angular:
提前 (AOT) 编译(又名 ng build --prod)分离出所有 JavaScript 代码 来自 index.html 文件。不幸的是,CSS的处理不是 因为整洁和样式在所有组件中保持内联(请参阅this ticket 进行跟踪)。所以,我们不得不忍受不愉快的风格-src '不安全内联'。
对于脚本,如果我们愿意,还需要 'unsafe-inline' 插件工作。 angular/angular#26152会有办法 虽然:基于 nonce 的 CSP 与严格动态的组合 指示。因此,如果一个受 nonce 信任的脚本创建一个新的 脚本在运行时,这个新脚本也将被认为是合法的。
因此,根据 Angular 团队的建议,当前使用 CSP 标头的唯一方法是使用 'unsafe-inline'
并进行一些重构(即不使用延迟加载的模块???太可怕了......)
【讨论】:
显然有一种方法可以使用“元组件”解决 CSP 标签限制? ***.com/a/59437992/4604645 。但它看起来很老套......如果你想尝试一下,我就把它留在这里以上是关于Angular 中动态编译的延迟加载动态路由导致“不安全评估”错误的主要内容,如果未能解决你的问题,请参考以下文章
Angular 8 - 延迟加载模块:错误 TS1323:仅当“--module”标志为“commonjs”或“esNext”时才支持动态导入