news 2026/5/15 1:26:05

Vue项目中使用DOMPurify防范富文本编辑器XSS攻击的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue项目中使用DOMPurify防范富文本编辑器XSS攻击的完整指南

概述

在现代Web应用开发中,富文本编辑器是常见的功能组件,但也是XSS(跨站脚本攻击)的主要入口之一。本文详细介绍如何在Vue项目中使用DOMPurify库来防范富文本编辑器的XSS安全风险。

XSS攻击风险分析

常见攻击方式

  • 脚本注入:用户输入<script>alert('XSS')</script>
  • 事件属性注入:用户输入<img src="x" onerror="alert('XSS')">
  • 链接注入:用户输入<a href="javascript:alert('XSS')">点击</a>

潜在危害

  • 窃取用户Cookie和登录凭证
  • 劫持用户会话
  • 修改页面内容误导用户
  • 传播恶意软件

DOMPurify简介

DOMPurify是一个现代化、快速、兼容性好的DOM净化库,专门用于防范XSS攻击。它基于白名单机制,只允许安全的HTML标签和属性通过。

主要特性

  • 零依赖
  • 快速高效
  • 高度可配置
  • 支持多种安全选项

安装和基本使用

1. 安装DOMPurify

1. 安装DOMPurify

npm install dompurify

2. 基本用法

import DOMPurify from 'dompurify'; const dirty = '<p>Some HTML with <script>alert("XSS")</script></p>'; const clean = DOMPurify.sanitize(dirty); console.log(clean); // '<p>Some HTML with </p>'

Vue项目中的集成实现

1. 创建安全过滤工具函数

// utils/sanitize.js import DOMPurify from 'dompurify'; const purifyConfig = { // 允许的安全标签 ALLOWED_TAGS: [ 'p', 'br', 'hr', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'u', 's', 'sub', 'sup', 'span', 'a', 'img', 'ul', 'ol', 'li', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'section', 'article', 'header', 'footer', 'nav', 'aside' ], // 允许的安全属性 ALLOWED_ATTR: [ 'href', 'src', 'alt', 'title', 'width', 'height', 'style', 'class', 'id', 'data-id', 'data-type', 'data-src', 'data-href' ], // 明确禁止的危险标签 FORBID_TAGS: ['script', 'object', 'embed', 'iframe', 'frame', 'frameset', 'applet', 'base', 'link', 'meta', 'style'], // 明确禁止的危险属性 FORBID_ATTR: [ 'onerror', 'onload', 'onmouseover', 'onclick', 'onfocus', 'onblur', 'onsubmit', 'onchange', 'javascript:', 'data:text/html', 'v-on:', '@', 'x-on:', 'ng-click', 'ng-bind-html' ], USE_PROFILES: { html: true }, SANITIZE_DOM: true }; export const sanitizeHTML = (html) => { if (!html || typeof html !== 'string') return ''; try { // 预处理:移除明显的脚本标签 let cleaned = html.replace(/<script[^>]*>.*?<\/script>/gi, '') .replace(/<script[^>]*\/>/gi, '') .replace(/javascript:[^"']*/gi, ''); // 使用DOMPurify进行深度净化 return DOMPurify.sanitize(cleaned, purifyConfig); } catch (error) { console.warn('DOMPurify sanitization failed:', error); return html.replace(/<script[^>]*>.*?<\/script>/gi, '').replace(/javascript:[^"']*/gi, ''); } };

2. 富文本编辑器组件集成

以下是一个完整的富文本编辑器组件,集成了DOMPurify安全过滤:

<!-- components/editor.vue --> <template> <div> <script :id="randomId" name="content" type="text/plain"> </script> </div> </template> <script> import _ from 'lodash'; import DOMPurify from 'dompurify'; // 安全配置 const purifyConfig = { ALLOWED_TAGS: [ 'p', 'br', 'hr', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'strong', 'em', 'u', 's', 'sub', 'sup', 'span', 'a', 'img', 'ul', 'ol', 'li', 'blockquote', 'code', 'pre', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'div', 'section', 'article', 'header', 'footer', 'nav', 'aside' ], ALLOWED_ATTR: [ 'href', 'src', 'alt', 'title', 'width', 'height', 'style', 'class', 'id', 'data-id', 'data-type', 'data-src', 'data-href' ], FORBID_TAGS: ['script', 'object', 'embed', 'iframe', 'frame', 'frameset', 'applet', 'base', 'link', 'meta', 'style'], FORBID_ATTR: [ 'onerror', 'onload', 'onmouseover', 'onclick', 'onfocus', 'onblur', 'onsubmit', 'onchange', 'javascript:', 'data:text/html', 'v-on:', '@', 'x-on:', 'ng-click', 'ng-bind-html' ], USE_PROFILES: { html: true }, SANITIZE_DOM: true }; // 安全过滤函数 const sanitizeHtml = (html) => { if (!html || typeof html !== 'string') return ''; try { let cleaned = html.replace(/<script[^>]*>.*?<\/script>/gi, '') .replace(/<script[^>]*\/>/gi, '') .replace(/javascript:[^"']*/gi, ''); return DOMPurify.sanitize(cleaned, purifyConfig); } catch (error) { console.warn('DOMPurify sanitization failed:', error); return html.replace(/<script[^>]*>.*?<\/script>/gi, '').replace(/javascript:[^"']*/gi, ''); } }; export default { name: 'VueUEditor', props: { ueditorPath: { type: String, default: 'static/umeditor/' }, name: { type: String, default: '' }, ueditorConfig: { type: Object, default: () => ({}) } }, data() { return { randomId: 'editor_1', instance: null, scriptTagStatus: 0, UEConfig: { autoHeightEnabled: false, allowDivTransToP: false, // UEditor内置过滤规则作为第一道防线 filterRules: { 'script': function() { return ''; }, '*': function(tag, attrs) { const filteredAttrs = {}; for(const attr in attrs) { if(!attr.startsWith('on')) { filteredAttrs[attr] = attrs[attr]; } } return { attrs: filteredAttrs }; } } } }; }, created() { if(this.name !== 'ud1'){ this.randomId = this.name; } this.UEConfig = _.defaultsDeep({}, this.UEConfig, this.ueditorConfig); if (window.UE !== undefined) { this.scriptTagStatus = 2; this.initEditor(); } else { this.insertScriptTag(); } }, beforeDestroy() { if (this.instance !== null && this.instance.destroy) { this.instance.destroy(); } }, methods: { // 获取安全内容 getSafeContent() { if (!this.instance) return ''; const rawContent = this.instance.getContent(); return sanitizeHtml(rawContent); }, // 设置安全内容 setSafeContent(content) { if (!this.instance) return; const safeContent = sanitizeHtml(content); this.instance.setContent(safeContent); }, insertScriptTag() { let editorScriptTag = document.getElementById('editorScriptTag'); let configScriptTag = document.getElementById('configScriptTag'); if (editorScriptTag === null) { configScriptTag = document.createElement('script'); configScriptTag.type = 'text/javascript'; configScriptTag.src = this.ueditorPath + 'umeditor.config.js'; configScriptTag.id = 'configScriptTag'; editorScriptTag = document.createElement('script'); editorScriptTag.type = 'text/javascript'; editorScriptTag.src = this.ueditorPath + 'umeditor.js'; editorScriptTag.id = 'editorScriptTag'; let s = document.getElementsByTagName('head')[0]; s.appendChild(configScriptTag); s.appendChild(editorScriptTag); } if (configScriptTag.loaded) { this.scriptTagStatus++; } else { configScriptTag.addEventListener('load', () => { this.scriptTagStatus++; configScriptTag.loaded = true; this.initEditor(); }); } if (editorScriptTag.loaded) { this.scriptTagStatus++; } else { editorScriptTag.addEventListener('load', () => { this.scriptTagStatus++; editorScriptTag.loaded = true; this.initEditor(); }); } this.initEditor(); }, initEditor() { if (this.scriptTagStatus === 2 && this.instance === null) { this.$nextTick(() => { this.instance = window.UM.getEditor(this.randomId, this.UEConfig); // 直接向实例添加安全方法 this.instance.getSafeContent = this.getSafeContent.bind(this); this.instance.setSafeContent = this.setSafeContent.bind(this); setTimeout(() => { this.$emit('ready', this.instance); }, 500) }); } } } }; </script>

3. 父组件中使用安全方法

<!-- 使用富文本编辑器的组件 --> <template> <div> <vue-editor :name="'ud1'" @storeUE="storeUE" :ueditor-config="ueditorConfig"></vue-editor> <button @click="saveContent">保存内容</button> </div> </template> <script> import VueEditor from '@/components/editor.vue'; export default { components: { VueEditor }, data() { return { editorInstance: null, ueditorConfig: { initialFrameWidth: '100%', initialFrameHeight: 560 } } }, methods: { storeUE(name, editor) { this.editorInstance = editor; // 保存编辑器实例 }, saveContent() { if (!this.editorInstance) return; // 使用安全方法获取内容 const safeContent = this.editorInstance.getSafeContent(); // 发送到服务器 this.sendToServer(safeContent); }, sendToServer(content) { // 发送经过安全过滤的内容到服务器 console.log('Sending safe content:', content); } } } </script>

安全最佳实践

1. 多层防护策略

  • 客户端过滤:使用DOMPurify进行前端过滤
  • 服务端验证:在服务器端再次进行安全验证
  • 内容安全策略(CSP):配置HTTP头限制脚本执行

2. 严格配置原则

  • 采用最小权限原则,只允许必要的HTML标签和属性
  • 定期审查和更新安全配置
  • 对不同类型的用户内容使用不同的安全策略

总结

通过在Vue项目中正确集成DOMPurify库,我们可以有效地防范富文本编辑器的XSS攻击风险。关键要点包括:

  1. 合理的配置:根据业务需求制定合适的白名单
  2. 双重过滤:客户端和服务端都进行安全过滤
  3. 持续监控:记录和分析安全事件
  4. 定期更新:随着业务发展调整安全策略

实施这套方案后,富文本编辑器将具备较强的安全防护能力,有效保护应用和用户免受XSS攻击的威胁。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 1:24:25

小白必看!轻松理解AI Agent,开启智能助理收藏之旅!

本文以通俗易懂的方式解释了AI Agent的概念、功能和区别于普通聊天机器人的特点。通过外卖骑手和实习生的比喻&#xff0c;形象地展示了Agent如何自主完成任务。文章还列举了自动驾驶、邮件自动回复、智能投顾等实际应用案例&#xff0c;强调Agent的广泛应用和强大能力。最后&a…

作者头像 李华
网站建设 2026/5/15 1:24:00

Claude代码分发与执行引擎:安全实现AI生成代码的自动化运行

1. 项目概述&#xff1a;一个专为Claude设计的代码分发与执行引擎最近在开发者社区里&#xff0c;一个名为win4r/claude-code-dispatch的项目引起了我的注意。乍一看这个标题&#xff0c;你可能会觉得它又是一个AI代码生成工具的简单封装&#xff0c;但深入研究后我发现&#x…

作者头像 李华
网站建设 2026/5/15 1:22:56

Claude与LSP融合:打造深度理解代码的智能编程助手

1. 项目概述&#xff1a;当Claude遇上LSP&#xff0c;一个专为代码理解而生的智能助手如果你是一名开发者&#xff0c;每天在IDE里敲代码时&#xff0c;是否曾幻想过有一个“懂行”的伙伴&#xff0c;不仅能帮你补全代码&#xff0c;还能真正理解你正在写的模块在整个项目中的意…

作者头像 李华
网站建设 2026/5/15 1:22:47

【AI伦理】人工智能的发展与伦理挑战深度解析

【AI伦理】人工智能的发展与伦理挑战深度解析 引言 人工智能技术的飞速发展正在深刻改变我们的生活方式、工作模式和社会结构。从智能助手到自动驾驶&#xff0c;从推荐系统到医疗诊断&#xff0c;AI已经渗透到各个领域。然而&#xff0c;随着AI能力的不断增强&#xff0c;一系…

作者头像 李华
网站建设 2026/5/15 1:21:35

CAD_Sketcher终极指南:如何在Blender中实现参数化CAD设计

CAD_Sketcher终极指南&#xff1a;如何在Blender中实现参数化CAD设计 【免费下载链接】CAD_Sketcher Constraint-based geometry sketcher for blender 项目地址: https://gitcode.com/gh_mirrors/ca/CAD_Sketcher 还在为Blender缺乏工程精度而烦恼吗&#xff1f;想象一…

作者头像 李华