CSP 禁用 unsafe-inline 后 Vue 的 click 事件为啥不生效了?
我在项目里加了 Content Security Policy,去掉了 ‘unsafe-inline’,结果页面上所有 @click 绑定的事件都失效了,控制台报错说被 CSP 阻止。但我不明白为啥 Vue 的事件也算 inline?
我试过把事件逻辑移到 methods 里,还是不行。是不是因为 Vue 在编译时生成了内联 handler?下面是最简复现代码:
<template>
<button @click="handleClick">点我</button>
</template>
<script>
export default {
methods: {
handleClick() {
alert('clicked');
}
}
}
</script>
onclick="..."属性,CSP 把它当 inline script 拦截了。你把逻辑移到 methods 里没用,因为 Vue 生成的 wrapper 函数才是被拦截的那个。Vue 的事件绑定本质上就是内联的,不管你的业务代码写在哪儿。
常见的解决方案:
一是用 CSP 的 nonce 机制。在 header 里设置
script-src 'nonce-xxxx',然后在 Vue 配置里把 nonce 传进去。Vue 3 可以用app.config.compilerOptions相关配置让生成的代码带上这个 nonce。不过 Vue 官方对 nonce 的支持比较有限,配置起来比较麻烦。二是用 hash 机制。计算你页面生成的 inline handler 的 hash,加到 CSP 里。但这不太实用,因为每次改代码 hash 就变了。
三是最简单粗暴的——接受现实,生产环境用事件委托。Vue 本身的事件绑定就是基于内联函数的,这是框架的设计决定。如果你要强上 CSP,通常需要在服务端渲染时动态注入 nonce,或者在构建时用插件处理。
说白了就是 Vue 2/3 的运行时编译都绕不开生成 inline handler,除非你用完全不同的架构(比如全部用事件委托手动绑定,不用模板里的 @click)。
最简单的解决办法:在 CSP 里用 nonce 放行。
服务器端(以 Express 为例)生成个随机字符串:
index.html 模板里给 script 标签加 nonce 属性:
Vue 3 需要在创建 app 时配置:
实际上 Vue 3 在启用 CSP 时会自动尝试从当前 script 标签读取 nonce。
如果上面这些嫌麻烦,还有个野路子:不用 @click,改为在 mounted 里用 addEventListener 绑定事件,这样就不是 inline handler 了。