Jsoup安全提取纯文本需先用Whitelist.clean()过滤再调.text(),避免XSS;Whitelist.simpleText()可去标签留文本,basic()保留p/br等安全标签,clean()后必须.text()而非toString()。

Jsoup怎么安全地去掉HTML标签只留文本
直接用 Jsoup.parse(html).text() 最快,但会丢掉换行、空格结构,且不防XSS——它只是提取文本,不清理危险属性或脚本。真正要“清洗后转纯文本”,得走 Whitelist + clean() 路线,再取 text()。
常见错误是以为 .text() 本身能防XSS:它确实不执行JS,但原始HTML里藏的 onerror="alert(1)" 或 javascript:alert() href 仍可能被其他系统误解析(比如前端二次渲染时)。
- 必须用
Jsoup.clean(html, whitelist)先过滤,再调.text() - 别用
Whitelist.none()—— 它允许所有标签,等于没洗 - 推荐用
Whitelist.simpleText()(只留文本节点)或Whitelist.basic()(保留 p/br/em/strong 等安全内联标签)
为什么 Jsoup.clean() 后还要再 .text()?
clean() 返回的是一个干净的 Document 对象,不是字符串。如果你直接 toString(),会得到带标签的HTML(只是已过滤),不是纯文本。
典型场景:富文本编辑器提交内容 → 后端清洗 → 存入数据库作摘要或搜索字段 → 这时要的是无格式字符串,不是“安全HTML”。
立即学习“Java免费学习笔记(深入)”;
- 正确链式写法:
Jsoup.clean(html, Whitelist.simpleText()).text() - 错误写法:
Jsoup.clean(html, Whitelist.simpleText()).toString()—— 结果还是"<p>hello</p>" -
Whitelist.simpleText()会删掉所有标签,只保留文本节点和空白,.text()才把它们拼成一行(内部自动 normalize 空格)
处理换行和段落缩进的现实问题
用户粘贴的HTML常含 <p>、<br>、<div>,单纯 .text() 会把它们全压成空格,导致“标题正文紧挨着”或“段落间没空行”。
这不是 Jsoup 的 bug,是设计使然:它按 DOM 文本节点规则合并,<p>A</p><p>B</p> 的两个文本节点之间本来就没有换行符。
- 若需保留段落分隔,改用
Jsoup.parse(html).select("p, br, div").stream()...手动遍历拼接(较重) - 轻量方案:用
Whitelist.basic().addTags("p", "br"),再.body().html()得到简化HTML,最后用正则替换<p>为"\n"、<br>为"\n" - 注意:正则替换前务必确保 HTML 已 clean 过,否则可能引入 XSS
Java 17+ 项目里 Jsoup 的兼容性坑
新版 Jsoup(1.17+)默认启用更严格的 HTML5 解析,对自闭合标签(如 <img/>)或缺失引号的属性(class=foo)容忍度变高,但老版本(1.15.x)在遇到 <script>alert()</script> 时可能把整个 script 内容当文本留下(未触发移除逻辑)。
错误现象:Jsoup.clean("<script>alert(1)</script>", Whitelist.none()) 在 1.15.3 返回空串,但在某些旧 build 中返回 "alert(1)"(因未识别 script 标签为可执行上下文)。
- 强制指定版本:Maven 里明确用
1.17.2或更高(截至 2024 年底最新稳定版) - 不要依赖传递依赖里的 Jsoup,尤其当项目用了 Spring Boot 2.x(自带 1.13.x)
- 测试时务必用真实含
onerror、javascript:、data:text/html的 payload 验证
最易被忽略的一点:Jsoup 不处理 CSS 表达式里的 XSS(比如 style="background:url(javascript:alert())"),Whitelist 默认也不检查 style 属性值。需要额外调用 .outputSettings().escapeMode(EscapeMode.base) 并配合自定义 NodeVisitor 扫描 style,但绝大多数业务场景其实只需禁用 style 标签和属性更简单——加一句 whitelist.removeAttributes(":all", "style") 就行。











