在 Vue 3 应用中渲染动态 HTML 内容时,安全风险不容忽视。DOMPurify 是一款强大的工具,能帮你有效抵御 XSS 攻击。下面我来为你讲解如何在 Vue 3 中使用 DOMPurify。
🛡️ Vue 3 中使用 DOMPurify 防御 XSS 攻击
1. 为什么需要 DOMPurify
在 Vue 3 中使用 v-html
指令直接渲染用户提供的 HTML 内容时,存在执行恶意脚本的风险(XSS 攻击)。DOMPurify 是一个开源的基于 DOM 的快速 XSS 净化工具,它通过解析递归元素节点来净化 HTML,输出安全的 HTML 内容。
2. 安装与基础用法
安装 DOMPurify
bash
npm install dompurify
基本净化使用
在 Vue 3 组件中,你可以这样使用 DOMPurify:
vue
<template>
<div>
<h2>净化后的HTML内容:</h2>
<div v-html="sanitizedHtml"></div>
</div>
</template>
<script>
import { defineComponent, ref, computed } from 'vue';
import DOMPurify from 'dompurify';
export default defineComponent({
setup() {
// 示例:可能不安全的HTML输入
const rawHtml = ref('<p>Hello, <a href="https://example.com">World</a>!</p><img src="x" οnerrοr="alert(1)">');
// 使用计算属性实时净化HTML
const sanitizedHtml = computed(() => {
return DOMPurify.sanitize(rawHtml.value);
});
return {
sanitizedHtml
};
}
});
</script>
效果说明
上述代码中,原始的 rawHtml
包含一个带有 onerror
属性的 img
标签,这是一种常见的 XSS 攻击向量。经过 DOMPurify 净化后,onerror
等危险属性会被移除,从而使其变得安全。
3. 高级配置与用法
DOMPurify 允许你通过配置选项自定义净化规则,以满足不同的安全需求。
使用配置选项
你可以定义一个配置对象来指定允许的标签和属性:
javascript
import DOMPurify from 'dompurify';
const config = {
// 只允许这些HTML标签
ALLOWED_TAGS: ['p', 'a', 'b', 'i', 'em', 'strong'],
// 只允许这些HTML属性
ALLOWED_ATTR: ['href', 'title', 'target'],
// 允许自定义URI的协议
ALLOWED_URI_REGEXP: /^(https?|ftp):/i
};
const dirtyHtml = '<p>Hello, <a href="https://example.com" target="_blank" οnclick="alert(1)">World</a>!</p>';
const cleanHtml = DOMPurify.sanitize(dirtyHtml, config);
// 输出: <p>Hello, <a href="https://example.com" target="_blank">World</a>!</p>
// 注意:onclick 等危险属性已被移除
使用钩子(Hooks)
DOMPurify 的钩子系统允许你在净化过程中进行更细粒度的控制。例如,你可以阻止特定的属性:
javascript
DOMPurify.addHook('uponSanitizeAttribute', function (node, data) {
// 示例:阻止所有JavaScript文件的URL
const regex = /^https?:\/\/.*\.js$/;
if (data.attrName === 'src' && node.nodeName === 'IMG' && regex.test(data.attrValue)) {
// 移除该属性
data.keepAttr = false;
}
});
4. 查看被移除的内容
在进行安全审计或调试时,你可能需要知道 DOMPurify 移除了哪些内容:
javascript
const dirtyInput = '<script>alert("XSS")</script><p>Safe content</p>';
const clean = DOMPurify.sanitize(dirtyInput);
// 查看被移除的节点
console.log(DOMPurify.removed);
// 可能会输出: [{element: script, attribute: null, reason: "not allowed"}]
请注意,DOMPurify.removed
主要用于调试,不建议在生产环境中依赖它进行业务逻辑判断。
5. 与其他 Vue 特性结合
与响应式数据结合
通常,你会将 DOMPurify 与 Vue 的响应式系统(如 ref
、computed
)结合使用:
vue
<template>
<div>
<textarea v-model="userInput" placeholder="输入HTML内容"></textarea>
<div v-html="sanitizedUserInput"></div>
</div>
</template>
<script>
import { defineComponent, ref, computed } from 'vue';
import DOMPurify from 'dompurify';
export default defineComponent({
setup() {
const userInput = ref('');
const sanitizedUserInput = computed(() => {
return DOMPurify.sanitize(userInput.value);
});
return {
userInput,
sanitizedUserInput
};
}
});
</script>
使用自定义指令
为了更方便地在模板中使用,你可以创建一个自定义指令:
javascript
// main.js 或类似文件
import { createApp } from 'vue';
import App from './App.vue';
import DOMPurify from 'dompurify';
const app = createApp(App);
app.directive('safe-html', (el, binding) => {
el.innerHTML = DOMPurify.sanitize(binding.value);
});
app.mount('#app');
在组件中使用自定义指令:
vue
<template>
<div v-safe-html="rawHtml"></div>
</template>
6. 服务端渲染(SSR)注意事项
在 Nuxt.js 等 SSR 环境中,由于 window
对象在服务端不可用,你需要动态导入 DOMPurify 或使用兼容的包装器:
javascript
import DOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
const window = new JSDOM('').window;
const purify = DOMPurify(window);
const clean = purify.sanitize(dirtyHtml);
7. 安全最佳实践
虽然 DOMPurify 提供了强大的客户端保护,但安全应该是多层次的:
-
首选文本插值 :对于非富文本内容,优先使用
{``{ }}
插值或v-text
,它们会自动转义 HTML。 -
原则:仅允许必要的标签和属性。
-
服务器端净化 :不要仅仅依赖客户端净化。在将内容存储到数据库之前,也应在服务器端进行类似的净化和验证。
-
内容安全策略 (CSP):实施严格的 CSP 作为额外的安全层,以减轻潜在的 XSS 影响。
-
保持更新:定期更新 DOMPurify 库以确保免受最新威胁。
8. 替代方案
在某些情况下,可以考虑其他方案:
-
专用的富文本编辑器组件:使用如 TipTap、Quill 或 Tiptap 等组件,它们通常内置了输出安全 HTML 的机制。
-
Vue 的渲染函数或 JSX:对于非常动态的 UI,使用这些方法可以更安全地构建 DOM 结构。
总结
DOMPurify 是 Vue 3 应用中处理不安全 HTML 并防御 XSS 攻击的强力工具。通过其简单的 API 和灵活的配置,它可以满足多种安全需求。关键在于牢记安全需要多层防御,切勿完全依赖客户端净化。