我有一个使用 Create-React-App (react-scripts) 和 Material-UI 的 React 应用程序。我想为我的应用程序应用强大的内容安全策略,不允许不安全的内联样式。
我想在服务器端设置 CSP 标头并提供一个随机数,这可以轻松完成。但是,Material-UI 在运行时动态设置某些内联
<style>
标签,而不将随机数值作为属性。
我已经在 Material-UI 网站上的指南和 csp 下看到了文档。他们似乎提供了一个解决方案。然而,该解决方案用于 HTML 的服务器端渲染,我没有使用它。我正在使用 Create-React-App 并静态交付 HTML、CSS 和 JavaScript。
有人知道如何实现吗?
JSS CSP 文档比MUI CSP 文档更好地解释了这一点,并提供了 Express 和 Webpack 的示例。基本上,您需要在名为
<meta>
的特殊 csp-nonce
属性(由 JSS 动态读取)和您的 Content-Security-Policy
标头(通过另一个 <meta>
或 HTTP 标头)中设置相同的“nonce” public/index.html
:
<meta http-equiv="Content-Security-Policy" content="default-src: 'self';
style-src: 'self' 'nonce-xxxxxxxxxxxxxxxx=='">
<meta property="csp-nonce" content="xxxxxxxxxxxxxxxx==" />
如果您可以在任何为您服务的
index.html
中动态地执行此操作,那么它就是安全的。 (显然,注入的脚本也可以动态读取随机数,但如果发生这种情况,你就已经输了)。如果您必须使用固定值(例如,因为您从 CDN 提供服务),那么它并不安全,但仍然可以说比 style-src: 'unsafe-inline'
更好,因为攻击者至少需要使用您特定于站点的随机数。
作为使用 CDN 时的混合方法,您可以在 CDN 从源获取页面时设置随机数,如此处使用 AWS Lambda@Edge 所示。那么你的随机数仅在 CDN 缓存 TTL 的特定区域中容易受到攻击(这应该是像
index.html
这样的可变资源的缩写)。
它就是这样工作的。即使浏览器中没有显示,随机数值也已设置。
尽管我们没有使用 SSR,但我可以通过部分遵循 SSR 的 MUI 文档来使其工作。
最初我尝试将 webpack_nonce 添加到我们的入口文件中,但这并没有将 nonce 应用于 mui 样式。
这对我有用:
const cache = createCache({
key: 'my-prefix-key',
nonce: nonce,
prepend: true,
});
function App(props) {
return (
<CacheProvider value={cache}>
<Home />
</CacheProvider>
);
}
如果您正在使用 webpack,这里有一个说明,说明如何至少在 SPA 中的每个构建中执行此操作:
<meta http-equiv="Content-Security-Policy" content="%%CSP_CONTENT%%">
csp-html-webpack-plugin
并进入你的webpack:const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CspHtmlWebpackPlugin = require('csp-html-webpack-plugin');
const generatedNonce = crypto.randomBytes(16).toString('base64');
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
'css-loader',
],
},
],
},
plugins: [
new webpack.DefinePlugin({
'process.env.EMOTIONAL_NONCE': JSON.stringify(generatedNonce),
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[hash].css',
chunkFilename: 'css/[id].[hash].css',
}),
new CspHtmlWebpackPlugin(
{
'base-uri': "'self'",
'default-src': [
"'self'"
],
'object-src': "'none'",
'script-src': "'self'",
'style-src': [
"'self'",
`'nonce-${generatedNonce}'`,
],
},
{
enabled: true,
hashEnabled: {
'style-src': false,
},
nonceEnabled: {
'style-src': false,
},
}
)
import createCache from '@emotion/cache';
const emotionalNonce = process.env.EMOTIONAL_NONCE;
const styleCache = createCache({
key: 'YourCacheKey',
nonce: emotionalNonce,
});
ReactDOM.render(
<CacheProvider value={styleCache}>
<Router>
<App />
</Router>
</CacheProvider>,
document.getElementById('root')
);
注意:对于动态元素(又名模态MUI Modals),如果您通过代码渲染它,请不要忘记创建另一个缓存提供程序或重用现有缓存提供程序并包装现有代码,因为它将不会插入默认情况下,您的 React 应用程序的根容器。