前言:在当今的 Web 开发中,安全始终是第一要务。
XSS
(跨站脚本攻击)作为最常见的 Web 安全威胁之一,时刻威胁着我们的应用。今天,我们将深入探讨DOMPurify
这个强大的安全工具,以及如何在 React 项目中正确使用它来保护我们的应用。
什么是 DOMPurify
?
DOMPurify
是一个专门用于 HTML、MathML 和 SVG 内容清理的 JavaScript 库。它由德国安全公司 Cure53 开发,是目前最受信任的客户端 HTML 清理工具之一。
核心特性
- 超快速度:专为性能优化设计
- 高兼容性:支持所有现代浏览器
- 零依赖:轻量级,无需额外依赖
白名单机制
:只允许安全的 HTML 元素和属性- 上下文感知:根据使用场景智能过滤
为什么需要 DOMPurify
?
React 的默认保护机制
React 默认会对所有动态内容进行转义,这是它的安全特性之一:
function App() {
const userInput = "<script>alert('XSS攻击')</script>";
return (
<div>
<h1>用户输入:</h1>
<p>{userInput}</p> {/* 安全:会被转义为文本 */}
</div>
);
}
当需要渲染 HTML 时
但在某些场景下,我们需要渲染 HTML 内容:
function BlogPost({ content }) {
// 危险!直接渲染HTML可能导致XSS攻击
return <div dangerouslySetInnerHTML={{ __html: content }} />;
}
这就是 DOMPurify
发挥作用的地方!
安装和基础使用
安装 DOMPurify
npm install dompurify
# 或者
yarn add dompurify
基础使用示例
import React from "react";
import DOMPurify from "dompurify";
function SafeHTMLRenderer({ htmlContent }) {
// 清理HTML内容
const cleanHTML = DOMPurify.sanitize(htmlContent);
return (
<div dangerouslySetInnerHTML={{ __html: cleanHTML }} className="content" />
);
}
// 使用示例
function App() {
const blogContent = `
<h2>欢迎来到我的博客</h2>
<p>这是一篇包含<strong>粗体</strong>和<em>斜体</em>的文章。</p>
<script>alert('恶意脚本')</script>
`;
return (
<div>
<h1>博客文章</h1>
<SafeHTMLRenderer htmlContent={blogContent} />
</div>
);
}
高级配置和自定义
自定义允许的标签和属性
import DOMPurify from "dompurify";
function CustomSanitizer({ content }) {
const config = {
ALLOWED_TAGS: ["p", "br", "strong", "em", "a", "img"],
ALLOWED_ATTR: ["href", "src", "alt", "title"],
ALLOW_DATA_ATTR: false, // 禁用data-*属性
ALLOW_ARIA_ATTR: true, // 允许aria-*属性
};
const cleanHTML = DOMPurify.sanitize(content, config);
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
自定义 URI 处理
function URISafeRenderer({ content }) {
const config = {
ALLOWED_URI_REGEXP:
/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
ADD_URI_SAFE_ATTR: ["href", "src"],
};
const cleanHTML = DOMPurify.sanitize(content, config);
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
实际应用场景
在富文本编辑器、评论系统、用户生成内容展示等场景中,DOMPurify 可以有效地防止 XSS 攻击,确保应用的安全性。
import React, { useState } from "react";
import DOMPurify from "dompurify";
function RichTextEditor() {
const [content, setContent] = useState("");
const handleContentChange = (e) => {
setContent(e.target.value);
};
const previewContent = DOMPurify.sanitize(content);
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 p-4">
<div className="max-w-7xl mx-auto">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold text-gray-800 mb-2">
DOMPurify 安全编辑器
</h1>
<p className="text-gray-600">
输入 HTML 内容,实时预览安全过滤后的结果
</p>
</div>
{/* 主容器 */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6 h-[calc(100vh-200px)]">
{/* 编辑器面板 */}
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
<div className="bg-gradient-to-r from-blue-500 to-indigo-600 px-6 py-4">
<h3 className="text-xl font-semibold text-white flex items-center">
编辑器
</h3>
</div>
<div className="p-6 h-full">
<textarea
value={content}
onChange={handleContentChange}
placeholder="输入HTML内容,例如:<h1>标题</h1><p>这是一段文字</p><script>alert('恶意代码')</script>"
rows={10}
className="w-full h-full resize-none border border-gray-300 rounded-lg px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent outline-none transition-all duration-200 font-mono text-sm bg-gray-50 hover:bg-white focus:bg-white"
/>
</div>
</div>
{/* 预览面板 */}
<div className="bg-white rounded-xl shadow-lg border border-gray-200 overflow-hidden">
<div className="bg-gradient-to-r from-green-500 to-emerald-600 px-6 py-4">
<h3 className="text-xl font-semibold text-white flex items-center">
预览
</h3>
</div>
<div className="p-6 h-full overflow-auto">
<div
className="preview-content min-h-full p-4 border border-gray-200 rounded-lg bg-gray-50 prose prose-sm max-w-none"
dangerouslySetInnerHTML={{ __html: previewContent }}
/>
</div>
</div>
</div>
</div>
</div>
);
}
export default RichTextEditor;

自定义 Hook 封装
为了更好的代码复用,我们可以创建一个自定义 Hook:
import { useMemo } from "react";
import DOMPurify from "dompurify";
function useSanitizedHTML(htmlContent, config = {}) {
return useMemo(() => {
return DOMPurify.sanitize(htmlContent, config);
}, [htmlContent, config]);
}
// 使用示例
function SafeContent({ content }) {
const sanitizedContent = useSanitizedHTML(content, {
ALLOWED_TAGS: ["p", "br", "strong", "em"],
ALLOWED_ATTR: [],
});
return <div dangerouslySetInnerHTML={{ __html: sanitizedContent }} />;
}
安全最佳实践
1. 服务器端验证
// 客户端清理
function ClientSideSanitization({ serverContent }) {
const cleanHTML = DOMPurify.sanitize(serverContent);
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
// 服务器端也应该进行验证
// Node.js示例
const DOMPurify = require("dompurify");
const { JSDOM } = require("jsdom");
const window = new JSDOM("").window;
const purify = DOMPurify(window);
app.post("/api/content", (req, res) => {
const { content } = req.body;
// 服务器端清理
const sanitizedContent = purify.sanitize(content);
// 保存到数据库
saveContent(sanitizedContent);
res.json({ success: true });
});
2. Content Security Policy
(CSP)
<!-- 在HTML头部添加CSP -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';"
/>
3. 输入验证
function validateAndSanitize(input) {
// 1. 基础验证
if (!input || typeof input !== "string") {
return "";
}
// 2. 长度限制
if (input.length > 10000) {
throw new Error("内容过长");
}
// 3. 清理HTML
return DOMPurify.sanitize(input);
}
常见陷阱和注意事项
1. 不要过度信任
// ❌ 错误:完全信任服务器内容
function BadExample({ serverContent }) {
return <div dangerouslySetInnerHTML={{ __html: serverContent }} />;
}
// ✅ 正确:始终进行清理
function GoodExample({ serverContent }) {
const cleanHTML = DOMPurify.sanitize(serverContent);
return <div dangerouslySetInnerHTML={{ __html: cleanHTML }} />;
}
2. 注意配置的安全性
// ❌ 危险配置
const dangerousConfig = {
ALLOWED_TAGS: ["script", "iframe"], // 允许危险标签
ALLOWED_ATTR: ["onload", "onerror"], // 允许事件处理器
};
// ✅ 安全配置
const safeConfig = {
ALLOWED_TAGS: ["p", "br", "strong", "em"],
ALLOWED_ATTR: ["href", "src", "alt", "title"],
};
3. 处理动态内容
function DynamicContent({ userContent, adminContent }) {
// 用户内容需要严格清理
const userHTML = DOMPurify.sanitize(userContent, {
ALLOWED_TAGS: ["p", "br"],
ALLOWED_ATTR: [],
});
// 管理员内容可以稍微宽松一些
const adminHTML = DOMPurify.sanitize(adminContent, {
ALLOWED_TAGS: ["p", "br", "strong", "em", "a", "img"],
ALLOWED_ATTR: ["href", "src", "alt", "title"],
});
return (
<div>
<div dangerouslySetInnerHTML={{ __html: userHTML }} />
<div dangerouslySetInnerHTML={{ __html: adminHTML }} />
</div>
);
}
总结
DOMPurify
是 React 应用中处理 HTML 内容安全的重要工具。通过正确使用 DOMPurify
,我们可以:
- 保护应用安全:防止 XSS 攻击
- 保持内容完整性:在安全的前提下保留必要的 HTML 格式
- 提升用户体验:支持富文本内容显示
- 简化开发流程:提供简单易用的 API
关键要点
- ✅ 始终对用户输入进行清理
- ✅ 根据使用场景配置合适的清理规则
- ✅ 结合服务器端验证使用
- ✅ 定期更新
DOMPurify
版本 - ❌ 不要完全信任任何外部内容
- ❌ 不要使用过于宽松的配置
安全是一个持续的过程,而不是一次性的任务。保持警惕,定期审查,确保你的应用始终安全可靠!
原文链接:https://code.ifrontend.net/archives/1378,转载请注明出处。
评论0