From fb2841784bb0da661ae3a4350eb246c502350fd7 Mon Sep 17 00:00:00 2001
From: AprilWind <2100166581@qq.com>
Date: Wed, 13 Aug 2025 13:24:31 +0800
Subject: [PATCH] =?UTF-8?q?update=20=E6=96=B0=E5=A2=9E=20HTML/XSS=20?=
=?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=B7=A5=E5=85=B7=E7=B1=BB=EF=BC=8C=E6=94=AF?=
=?UTF-8?q?=E6=8C=81=E5=A4=9A=E7=A7=8D=E8=BF=87=E6=BB=A4=E7=AD=96=E7=95=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
pom.xml | 7 +
ruoyi-common/ruoyi-common-core/pom.xml | 5 +
.../common/core/utils/HtmlSanitizerUtil.java | 165 ++++++++++++++++++
3 files changed, 177 insertions(+)
create mode 100644 ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java
diff --git a/pom.xml b/pom.xml
index 7aabad563..59acf96f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,6 +36,7 @@
0.2.0
1.18.38
1.80
+ 20240325.1
1.16.7
2.7.0
@@ -288,6 +289,12 @@
${bouncycastle.version}
+
+ com.googlecode.owasp-java-html-sanitizer
+ owasp-java-html-sanitizer
+ ${owasp.html-sanitizer.version}
+
+
io.github.linpeilie
mapstruct-plus-spring-boot-starter
diff --git a/ruoyi-common/ruoyi-common-core/pom.xml b/ruoyi-common/ruoyi-common-core/pom.xml
index ad37e90db..6048398f3 100644
--- a/ruoyi-common/ruoyi-common-core/pom.xml
+++ b/ruoyi-common/ruoyi-common-core/pom.xml
@@ -94,6 +94,11 @@
ip2region
+
+ com.googlecode.owasp-java-html-sanitizer
+ owasp-java-html-sanitizer
+
+
diff --git a/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java
new file mode 100644
index 000000000..a1f8ba51d
--- /dev/null
+++ b/ruoyi-common/ruoyi-common-core/src/main/java/org/dromara/common/core/utils/HtmlSanitizerUtil.java
@@ -0,0 +1,165 @@
+package org.dromara.common.core.utils;
+
+import org.owasp.html.HtmlPolicyBuilder;
+import org.owasp.html.PolicyFactory;
+import org.owasp.html.Sanitizers;
+
+/**
+ * HTML/XSS 过滤工具类
+ * 基于 OWASP Java HTML Sanitizer
+ *
+ * @author AprilWind
+ */
+public class HtmlSanitizerUtil {
+ /**
+ * 纯文本策略:
+ * 去除所有 HTML 标签,仅保留纯文本内容,
+ * 适用于对内容格式无任何要求,只需要纯文本的场景,如用户名、标题等
+ */
+ public static final PolicyFactory TEXT_ONLY_POLICY = new HtmlPolicyBuilder().toFactory();
+
+ /**
+ * 简单展示文本策略:
+ * 允许部分格式化标签,介于纯文本和富文本之间,
+ * 适用于简单文本展示,如昵称、简短描述等
+ */
+ public static final PolicyFactory SIMPLE_TEXT_POLICY = Sanitizers.FORMATTING;
+
+ /**
+ * 严格无脚本策略:
+ *
+ * 仅允许非常基础的格式化标签,如加粗(b/strong)、斜体(i/em)、下划线(u)、
+ * 段落(p)、换行(br)和列表(ul/ol/li)等,禁止所有标签属性,
+ * 包括样式(style)、事件处理器(onXXX)或其他可能引入 XSS 的内容
+ *
+ *
+ * 适用于对安全要求极高且不需要任何复杂格式的场景,
+ * 如纯文本内容的最小格式化需求
+ *
+ */
+ public static final PolicyFactory STRICT_NO_SCRIPT_POLICY = new HtmlPolicyBuilder()
+ .allowElements("b", "strong", "i", "em", "u", "p", "br", "ul", "ol", "li")
+ // 显式声明允许文本出现在这些标签中,增强策略清晰度(可选)
+ .allowTextIn("b", "strong", "i", "em", "u", "p", "ul", "ol", "li")
+ .toFactory();
+
+ /**
+ * 评论区策略:
+ * 允许基础格式化标签和超链接,
+ * 防止图片、表格等复杂内容,
+ * 适用于评论区文本,既保证安全,又支持简单的文本格式和链接
+ */
+ public static final PolicyFactory COMMENT_POLICY = Sanitizers.FORMATTING
+ .and(Sanitizers.LINKS);
+
+ /**
+ * 文章编辑器策略:
+ * 允许格式化标签、超链接、图片、样式、块级元素和表格,
+ * 适用于富文本编辑器内容,如文章正文,兼顾内容丰富和安全
+ */
+ public static final PolicyFactory ARTICLE_POLICY = Sanitizers.FORMATTING
+ .and(Sanitizers.LINKS)
+ .and(Sanitizers.IMAGES)
+ .and(Sanitizers.STYLES)
+ .and(Sanitizers.BLOCKS)
+ .and(Sanitizers.TABLES);
+
+ /**
+ * 富文本邮件内容策略:
+ * 允许基础格式化标签、超链接、图片、表格和样式,
+ * 相较于一般富文本编辑器稍微严格一些,主要用于邮件内容的安全过滤,
+ * 防止恶意脚本注入,同时保证邮件内容的丰富展示效果
+ */
+ public static final PolicyFactory EMAIL_HTML_POLICY = Sanitizers.FORMATTING
+ .and(Sanitizers.LINKS)
+ .and(Sanitizers.IMAGES)
+ .and(Sanitizers.STYLES)
+ .and(Sanitizers.TABLES);
+
+ /**
+ * CMS 编辑器策略:较宽松,允许多种标签和属性
+ *
+ * 该策略适用于内容管理系统中的富文本编辑器,支持多样化的HTML标签和常用属性,
+ * 包括超链接、图片、表格、媒体标签等,同时允许安全的样式和部分自定义属性。
+ *
+ *
+ * - 允许常见的结构化标签,如段落(p)、标题(h1-h6)、列表(ul,ol,li)、表格(table等)
+ * - 支持超链接(a)的href属性,图片(img)的src、alt等属性
+ * - 支持iframe、video、audio等媒体标签及其常用属性
+ * - 允许表格单元格的合并属性colspan、rowspan
+ * - 通过allowStyling方法,允许内联样式并进行安全过滤
+ * - 限制URL协议为http、https和mailto,避免javascript等危险协议
+ * - 支持部分常用的自定义数据属性(data-toggle、data-target、data-id)以便前端交互
+ * - 支持无障碍相关的aria-*属性和role属性,提升辅助设备兼容性
+ * - 允许iframe的特定属性allowfullscreen和frameborder以支持全屏和边框控制
+ *
+ */
+ public static final PolicyFactory CMS_EDITOR_POLICY = new HtmlPolicyBuilder()
+ .allowElements(
+ "a", "b", "blockquote", "br", "caption", "cite", "code",
+ "col", "colgroup", "dd", "div", "dl", "dt", "em", "h1",
+ "h2", "h3", "h4", "h5", "h6", "i", "img", "li", "ol",
+ "p", "pre", "q", "small", "strike", "strong", "sub",
+ "sup", "table", "tbody", "td", "tfoot", "th", "thead",
+ "tr", "u", "ul", "span", "iframe", "video", "audio", "source")
+ .allowAttributes("href").onElements("a")
+ .allowAttributes("src", "alt", "title", "width", "height", "type", "controls").onElements("img", "iframe", "video", "audio", "source")
+ .allowAttributes("colspan", "rowspan").onElements("td", "th")
+ .allowStyling() // 允许安全的内联样式,过滤危险CSS
+ .allowUrlProtocols("http", "https", "mailto") // 限制URL协议,防止注入javascript等
+ .allowAttributes("data-toggle", "data-target", "data-id").onElements("*") // 支持部分自定义 data-* 属性,方便前端交互
+ .allowAttributes("aria-label", "aria-hidden", "aria-expanded", "aria-pressed").onElements("*") // 支持无障碍辅助属性
+ .allowAttributes("role").onElements("*") // 支持无障碍role属性
+ .allowAttributes("allowfullscreen", "frameborder").onElements("iframe") // iframe 支持全屏及边框属性
+ .toFactory();
+
+ /**
+ * 纯文本过滤,去除所有 HTML 标签
+ */
+ public static String sanitizeToText(String htmlContent) {
+ return htmlContent == null ? null : TEXT_ONLY_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * 简单文本过滤,允许部分格式
+ */
+ public static String sanitizeForSimpleText(String htmlContent) {
+ return htmlContent == null ? null : SIMPLE_TEXT_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * 严格无脚本过滤,只允许非常基础标签,无任何属性
+ */
+ public static String sanitizeStrictNoScript(String htmlContent) {
+ return htmlContent == null ? null : STRICT_NO_SCRIPT_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * 评论区 HTML 过滤
+ */
+ public static String sanitizeForComment(String htmlContent) {
+ return htmlContent == null ? null : COMMENT_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * 文章编辑器 HTML 过滤
+ */
+ public static String sanitizeForArticle(String htmlContent) {
+ return htmlContent == null ? null : ARTICLE_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * 邮件富文本过滤
+ */
+ public static String sanitizeForEmailHtml(String htmlContent) {
+ return htmlContent == null ? null : EMAIL_HTML_POLICY.sanitize(htmlContent);
+ }
+
+ /**
+ * CMS 编辑器过滤,允许较多标签及属性
+ */
+ public static String sanitizeForCmsEditor(String htmlContent) {
+ return htmlContent == null ? null : CMS_EDITOR_POLICY.sanitize(htmlContent);
+ }
+
+}
--
Gitee