所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

React + DOMPurify:高性能HTML安全渲染解决方案 – 零依赖安全防护

前言:在当今的 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内容,例如:&#60;h1&#62;标题&#60;/h1&#62;&#60;p&#62;这是一段文字&#60;/p&#62;&#60;script&#62;alert('恶意代码')&#60;/script&#62;"
                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,我们可以:

  1. 保护应用安全:防止 XSS 攻击
  2. 保持内容完整性:在安全的前提下保留必要的 HTML 格式
  3. 提升用户体验:支持富文本内容显示
  4. 简化开发流程:提供简单易用的 API

关键要点

  • ✅ 始终对用户输入进行清理
  • ✅ 根据使用场景配置合适的清理规则
  • ✅ 结合服务器端验证使用
  • ✅ 定期更新 DOMPurify 版本
  • ❌ 不要完全信任任何外部内容
  • ❌ 不要使用过于宽松的配置

安全是一个持续的过程,而不是一次性的任务。保持警惕,定期审查,确保你的应用始终安全可靠!

原文链接:https://code.ifrontend.net/archives/1378,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?