
本文澄清一个常见误区:将 PDF 字节流错误地转为 HTML 字符串并进行 HTML 实体转义,不仅无法防范 XSS,反而会破坏二进制文件结构;正确做法是直接以 application/pdf 类型响应原始字节流。
本文澄清一个常见误区:将 pdf 字节流错误地转为 html 字符串并进行 html 实体转义,不仅无法防范 xss,反而会破坏二进制文件结构;正确做法是直接以 `application/pdf` 类型响应原始字节流。
PDF 是二进制格式,不是 HTML 文本。您当前的代码:
String x = IOUtils.toString(getPdf(), "UTF-8"); // ❌ 危险!强制解码二进制为字符串 String secureX = replaceXssCharacters(x); // ❌ 对 PDF 作 HTML 转义毫无意义且破坏数据 return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(secureX); // ❌ 媒体类型与内容严重不匹配
存在三重问题:
- 数据损坏:IOUtils.toString(..., "UTF-8") 尝试用 UTF-8 解析 PDF 的二进制字节,必然产生乱码或 MalformedInputException,导致 PDF 无法打开;
-
逻辑错误:XSS 防护仅适用于输出到 HTML 上下文的动态字符串(如 ${userInput}),而 PDF 本身不执行 JavaScript、不解析 HTML 标签,对 PDF 内容做 HTML 实体转义既无安全收益,又彻底破坏文件结构;
- Content-Type 错误:返回 text/html 却塞入 PDF 二进制(或已损坏的字符串),浏览器将尝试解析为 HTML,显示乱码或空白,而非 PDF 查看器。
✅ 正确做法:保持字节流完整性,设置正确的媒体类型:
@GetMapping("/pdf")
public ResponseEntity<resource> getPdf() throws IOException {
byte[] pdfBytes = getPdf(); // 直接获取原始 byte[]
ByteArrayInputStream bais = new ByteArrayInputStream(pdfBytes);
Resource resource = new InputStreamResource(bais);
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_PDF) // ✅ 关键:使用 application/pdf
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"document.pdf\"")
.body(resource);
}</resource>
⚠️ 注意事项:
- 若需在网页中嵌入 PDF,应使用 或
- 真正的 XSS 防护场景是:当 PDF 元数据(如作者、标题)来自用户输入,且这些字段后续被拼接到 HTML 页面中展示时,才需对元数据做 HTML 转义;
- 不要对任何二进制资源(PDF、图片、ZIP 等)执行字符串转换或 HTML 编码——它们不属于 HTML 渲染上下文。
总之:PDF 不是 HTML,无需也不应 HTML 转义;它应作为二进制流原样传输,并声明 application/pdf 类型。 安全始于对数据类型与上下文的正确认知。











