在 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 和灵活的配置,它可以满足多种安全需求。关键在于牢记安全需要多层防御,切勿完全依赖客户端净化。