返回更新列表
新发布Sep 22, 2026

java-html-sanitizer v20260921.1

获取第三方 HTML 并生成可安全嵌入到 Web 应用中的 HTML。快速且易于配置。

分享

OWASP Java HTML Sanitizer

Java CI with Maven Coverage Status CII Best Practices Maven Central

一个快速且易于配置、用 Java 编写的 HTML 清理器,可让你 在 Web 应用中包含由第三方编写的 HTML, 同时防范 XSS。

现有依赖是 JSR 305。其他 jar 仅供测试套件使用。JSR 305 依赖是 仅编译期依赖,只用于注解。

此代码的编写秉持安全最佳实践,拥有 广泛的测试套件,并已通过 对抗性安全审查

目录

快速入门

快速入门包含无论是否使用 Maven 都能开始使用的说明。

预置策略

你可以使用 预置策略:

PolicyFactory policy = Sanitizers.FORMATTING.and(Sanitizers.LINKS);
String safeHTML = policy.sanitize(untrustedHTML);

制定策略

测试用例 展示了如何配置你自己的 策略:

PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("a")
    .allowUrlProtocols("https")
    .allowAttributes("href").onElements("a")
    .requireRelNofollowOnLinks()
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);

自定义策略

你可以编写 自定义策略 来实现诸如将 h1 改为带特定 class 的 div 之类的操作:

PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("p")
    .allowElements(
        (String elementName, List<String> attrs) -> {
          // Add a class attribute.
          attrs.add("class");
          attrs.add("header-" + elementName);
          // Return elementName to include, null to drop.
          return "div";
        }, "h1", "h2", "h3", "h4", "h5", "h6")
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);

请注意,如果你希望元素 "a"、"font"、"img"、"input" 和 "span" 在不包含任何属性时也能通过过滤器, 则必须使用 allowWithoutAttributes() 方法 将它们显式加入白名单。

属性策略同样允许运行自定义代码。添加属性策略不会削弱任何默认策略,例如 style 或 URL 属性检查。

new HtmlPolicyBuilder = new HtmlPolicyBuilder()
    .allowElement("div", "span")
    .allowAttributes("data-foo")
        .matching(
            (String elementName, String attributeName, String value) -> {
              // Return value for the attribute or null to drop.
            })
        .onElements("div", "span")
    .build()

预处理器

预处理器允许插入文本以及进行大规模的结构性变更。

new HtmlPolicyBuilder = new HtmlPolicyBuilder()
    // Use a preprocessor to be backwards compatible with the
    // <plaintext> element which 
    .withPreprocessor(
        (HtmlStreamEventReceiver r) -> {
          // Provide user with info about links before they click.
          // Before:                       <a href="https://example.com/...">
          // After:  (https://example.com) <a href="https://example.com/...">
          return new HtmlStreamEventReceiverWrapper(r) {
            @Override public void openTag(String elementName, List<String> attrs) {
              if ("a".equals(elementName)) {
                for (int i = 0, n = attrs.size(); i < n; i += 2) {
                  if ("href".equals(attrs.get(i)) {
                    String url = attrs.get(i + 1);
                    String origin;
                    try {
                      URI uri = new URI(url);
                      String scheme = uri.getScheme();
                      String authority = uri.getRawAuthority();
                      if (scheme == null && authority == null) {
                        origin = null;
                      } else {
                        origin = (scheme != null ? scheme + ":" : "")
                               + (authority != null ? "//" + authority : "");
                      }
                    } catch (URISyntaxException ex) {
                      origin = "about:invalid";
                    }
                    if (origin != null) {
                      text(" (" + origin + ") ");
                    }
                  }
                }
              }
              super.openTag(elementName, attrs);
            }
          };
        }
    .allowElement("a")
    ...
    .build()

预处理发生在策略应用之前,因此不会影响 输出的安全性。

遥测

当策略拒绝某个元素或属性时,它会通知一个 HtmlChangeListener

你可以借此跟踪策略违规趋势,并发现何时有人在 试图突破你的安全防护。

PolicyFactory myPolicyFactory = ...;
// If you need to associate reports with some context, you can do so.
MyContextClass myContext = ...;

String sanitizedHtml = myPolicyFactory.sanitize(
    unsanitizedHtml,
    new HtmlChangeListener<MyContextClass>() {
      @Override
      public void discardedTag(MyContextClass context, String elementName) {
        // ...
      }
      @Override
      public void discardedAttributes(
          MyContextClass context, String elementName, String... attributeNames) {
        // ...
      }
    },
    myContext);

注意:如果某个字符串在清理时未产生任何变更通知, 这并不意味着输入字符串就一定可以安全使用。 请只使用清理器的输出。

清理器确保输出属于常用 HTML 解析器 对其含义能够达成一致的 HTML 子集,但没有任何通知 并不意味着输入就属于这样的子集, 只能说明其中不包含被移除的元素或属性。

有关此主题的更多信息,请参阅《既然可以验证,为何还要清理?》

有疑问?

如果你想报告漏洞,请参阅 AttackReviewGroundRules

订阅 邮件列表, 以便获知已知漏洞和重要更新。

贡献

如果你想参与贡献,请联系 @mvsamuel@manicode

我们欢迎问题报告和 PR。 改变行为或新增功能的 PR 应同时包含正向测试和 负向测试

请注意,贡献内容遵循 Apache 2.0 许可证

致谢

感谢每一位提供批评意见和代码帮助的人

分类