1.Vite插件(vitePlugins.push(cspPlugin(isBuild));)
csp.ts
import type { Plugin } from 'vite';
export function cspPlugin(mode: boolean): Plugin[] {
// mode 执行方式:true为打包 ,false为本地运行
const nonce = mode ? '__SERVER-GENERATED-NONCE__' : 'nonce';
// const nonce = mode ? 'UzIeWf1T3ifABGK' : 'nonce';
const regexScript = /<script(.*?)/gi;
const replacementScript = `<script nonce="${nonce}"$1`;
const regexStyle = /<style(.*?)/gi;
const replacementStyle = `<style nonce="${nonce}"$1`;
const regexLink = /<link(.*?)/gi;
const replacementLink = `<link nonce="${nonce}"$1`;
const fs = require('fs');
if (fs.existsSync('log.txt')) fs.rmSync('log.txt');
if (fs.existsSync('log1.txt')) fs.rmSync('log1.txt');
if (fs.existsSync('log2.txt')) fs.rmSync('log2.txt');
// something
const viteClientRegex = /node_modules\/vite\/dist\/client\/client\.mjs$/gi;
// antd-vue dynamicCss version 4.1.1
const antdvCsp = /node_modules\/ant-design-vue\/es\/vc-util\/Dom\/dynamicCss\.js$/gi;
// antd icons-vue version 3.3.11
const iconRegex = /node_modules\/@ant-design\/icons-vue\/es\/utils\.js$/gi;
// vite-plugin-theme version 0.8.6
const themeRegex = /node_modules\/@rys-fe\/vite-plugin-theme\/es\/client\.js$/gi;
// vite-plugin-svg-icons version 4.5.2
const svgRegex = /svg-icons-register/gi;
const transformStr: Plugin = {
name: 'transform-file',
transform(src, id) {
// fs.appendFileSync('log.txt', '----------------' + id + '\r\n' + src + '\r\n' + '----------' + '\r\n', function (err) {
// if (err) {
// return console.log(err);
// }
// console.log('The file was saved!');
// });
if (viteClientRegex.test(id)) {
return {
code: src.replace(
"style.setAttribute('data-vite-dev-id', id);",
`style.setAttribute('data-vite-dev-id', id); style.setAttribute('nonce', '${nonce.toString()}');`
),
};
}
if (antdvCsp.test(id)) {
return {
code: src.replace(' const {\n csp,', `option.csp = {nonce:"__SERVER-GENERATED-NONCE__"}; const {\n csp,`),
};
}
if (iconRegex.test(id)) {
return {
code: src.replace('csp.value', `csp && csp.value ? csp.value : {nonce:"__SERVER-GENERATED-NONCE__"}`),
};
}
if (themeRegex.test(id)) {
return {
code: src
.replace("style.setAttribute('id', id);", `style.setAttribute('id', id);style.setAttribute("nonce", "__SERVER-GENERATED-NONCE__");`)
.replace('styleDom.innerHTML = cssText;', "styleDom.innerHTML = cssText;styleDom.setAttribute('nonce', '__SERVER-GENERATED-NONCE__');"),
};
}
if (svgRegex.test(id)) {
return {
code: src
.replace(
"svgDom.setAttribute('xmlns:link','http://www.w3.org/1999/xlink');",
"svgDom.setAttribute('xmlns:link','http://www.w3.org/1999/xlink');svgDom.setAttribute('nonce','__SERVER-GENERATED-NONCE__');"
)
.replaceAll(/<symbol([\s\S]*?)><\/symbol>/g, (args) => {
return (
args
.replaceAll(/<style>/g, '<style nonce=\\"__SERVER-GENERATED-NONCE__\\">')
.replaceAll(/style=(\\)+"([^"]*fill):([^;>]*)/g, '$2=\\"$3\\"')
// .replaceAll(/style=\\"[^"]*fill:(#[0-9a-fA-F]+)[^"]*"/g, 'fill=\\"$1\\"')
.replaceAll(/style=(\\)+"([^"]*opacity):([^;>]*)/g, '$2=\\"$3\\"')
);
}),
};
}
},
};
const createTag: Plugin = {
name: 'html-inject-nonce-into-script-tag',
enforce: 'post',
transformIndexHtml(html) {
return html.replace(regexScript, replacementScript).replace(regexStyle, replacementStyle).replace(regexLink, replacementLink);
},
};
return [transformStr, createTag];
}
2.Nginx配置(include/csp.conf)
csp.conf
userid on;
set $cspNonce 'UzIeWf1T3ifABGK';
if ($http_cookie ~ "((?<=^.{4}).{4})" ){
set $cspNonce $1;
}
if ($http_cookie ~ "((?<=^.{12}).{4})" ){
set $cspNonce $cspNonce$1;
}
if ($http_cookie ~ "((?<=^.{17}).{4})" ){
set $cspNonce $cspNonce$1;
}
if ($http_cookie ~ "((?<=^.{10}).{4})" ){
set $cspNonce $cspNonce$1;
}
if ($cspNonce ~ "([^%&',;=?$\x22]+)" ){
set $cspNonce $1;
}
sub_filter __SERVER-GENERATED-NONCE__ $cspNonce;
sub_filter_types application/javascript;
sub_filter_once off;
add_header Content-Security-Policy "default-src 'self';script-src 'strict-dynamic' 'unsafe-eval' 'nonce-$cspNonce';style-src 'self' 'nonce-$cspNonce';img-src 'self' data: ; object-src 'self';font-src 'self' data:; frame-ancestors 'self';connect-src 'self'";
add_header X-Content-Type-Options "nosniff";
add_header X-XSS-Protection "1; mode=block";
#proxy_set_header Accept-Encoding '';
其它可行方式(未测试)
监听htmlElment的创建,并设置nonce
<script nonce="UzIeWf1T3ifABGK">
var container = document.querySelector("head")
var config = {childList: true}
// 当观察到变动时执行的回调函数
const callback = function (mutationsList, observer) {
// Use traditional 'for loops' for IE 11
for (let mutation of mutationsList) {
if (mutation.type === "childList") {
if (mutation.addedNodes && mutation.addedNodes[0]) {
mutation.addedNodes[0].setAttribute("nonce", "UzIeWf1T3ifABGK");
}
// console.log("A child node has been added or removed.");
} else if (mutation.type === "attributes") {
console.log("The " + mutation.attributeName + " attribute was modified.");
}
}
};
// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);
// 以上述配置开始观察目标节点
observer.observe(container, config);
</script>
评论区