jquick-pdf 图片嵌入:本地图片与网络图片的接入与安全边界

简介: jquick-pdf 通过 `<image src="...">` 简化图片嵌入,但安全关键在资源管控:本地路径需防越界,网络图片须经白名单校验、HTTPS 限制、类型/大小/超时控制后下载至受控目录。模板仅处理可信本地路径,杜绝 SSRF 与静默失败。

jquick-pdf 图片嵌入:本地图片与网络图片的接入与安全边界

引入

把图片放进 PDF,传统方案要自己管理字节流、尺寸和资源生命周期。jquick-pdf 用 <image src="..." alt="..."> 在模板里表达图片位置,渲染器负责绘制,模板更接近业务页面。真正的风险不在标签怎么写,而在图片来源是否受控:本地路径越界、把任意用户 URL 交给渲染器、下载失败后生成一份“看似成功”却没有照片的 PDF。本文聚焦图片资源的接入方式与安全边界。

环境基线为 io.github.paohaijiao:jquick-pdfx:4.1.0 与 JDK 8+。

核心讲解

图片元素与本地路径

位图使用 <image src="..." alt="...">;src 指向本地文件路径(如 D:/pdf/image.png)。源码 sample 已验证写法:

<image src="src/main/resources/image/img.png" style="width:200px;height:150px"></image>

完整示例:ImageDemo

import com.github.paohaijiao.executor.JQuickPdfFactory; // 导入工厂
import java.nio.file.*; // 导入文件工具

public class ImageDemo {
    // 声明类
  public static void main(String[] args) throws Exception {
    // 声明入口
    String template = "<pdf><body><h1>'产品图片'</h1>" // 创建模板
      + "<image src=\"src/main/resources/image/img.png\" style=\"width:200px;height:150px\"></image>" // 嵌入受控本地图片
      + "</body></pdf>"; // 结束模板
    byte[] pdf = new JQuickPdfFactory().executeContent(template); // 执行渲染
    Files.write(Paths.get("image-report.pdf"), pdf); // 保存 PDF
  }
}

本地图片的路径与尺寸配置

src 可以写相对路径,也可以写绝对路径;相对路径相对于进程工作目录,而不是源码目录,这一点在 IDE 与生产环境之间最容易踩坑。尺寸由 width、height 控制,单位支持 px、pt、mm、cm、in;建议两个维度都写,避免不同原图比例导致版面高度跳动。alt 是位图元素的替代文本属性,用来标注业务含义(如“设备正面照”),便于模板审阅与后续定位。是否可替换为其他图片格式,取决于运行环境对位图的解码支持。

网络图片先下载为受控本地文件

仓库没有验证渲染器直接读取 http/https 图片,因此 src不 可以直接写网络地址。保守且可落地的方案是:由后端用 HTTP 客户端把图片下载到受控本地目录,模板只接收该目录内的本地路径。下面是一个最小化实现,只用 JDK 自带能力(java.net、java.nio.file、java.util),把白名单、超时、类型与大小校验全部挡在进入模板之前:

private static Path downloadToLocal(String url, Set<String> allowHosts) throws Exception {
    // 下载并落地
    URL u = new URL(url); // 解析地址
    if (!"https".equals(u.getProtocol()) || !allowHosts.contains(u.getHost())) {
    // 协议与域名白名单
        throw new IllegalArgumentException("host not allowed"); // 非白名单直接拒绝
    }
    Path dir = Files.createTempDirectory("jquick-img-"); // 隔离的受控临时目录
    Path file = dir.resolve(UUID.randomUUID().toString() + ".png"); // 文件名由服务端生成
    HttpURLConnection conn = (HttpURLConnection) u.openConnection(); // 打开连接
    conn.setConnectTimeout(3000); // 连接超时 3s
    conn.setReadTimeout(5000); // 读取超时 5s
    String type = conn.getContentType(); // 响应类型
    if (type == null || !type.startsWith("image/")) {
    // 类型校验
        throw new IllegalArgumentException("not an image"); // 拒绝非图片响应
    }
    long limit = 5L * 1024 * 1024; // 字节上限 5MB
    try (InputStream in = conn.getInputStream(); // 输入流
         OutputStream out = Files.newOutputStream(file)) {
    // 输出流
        byte[] buf = new byte[8192]; // 拷贝缓冲
        long total = 0; int n; // 累计字节数
        while ((n = in.read(buf)) > 0) {
    // 边读边计
            total += n; // 累加
            if (total > limit) {
    throw new IllegalArgumentException("image too large"); } // 超限立即中断
            out.write(buf, 0, n); // 写入受控目录
        }
    }
    return file; // 返回受控本地路径
}

调用方拿到的绝对路径拼进模板后,渲染侧与本地图片完全一致。注意字节数并不能约束解码内存,像素上限(宽×高)需要在读取图片元数据后单独判断。

关键细节

下载环节的安全校验(SSRF 防护)

  • 域名白名单:只允许预先登记的域名或对象存储桶域名,拒绝 IP 直连、内网网段与非标准端口。
  • 协议与响应码:只接受 HTTPS,并校验响应码。
  • 重定向:限制跳转次数,每一跳都要重新校验目标主机是否仍在白名单内。
  • Content-Type:只接受 image/png、image/jpeg 等预期类型,不依赖文件扩展名判断。
  • 大小与超时:设置连接超时与读取超时;下载时按字节累计,超过上限立即中断,避免超大文件打满磁盘或内存。
  • 落地路径:写入隔离的受控临时目录,文件名由服务端生成,不使用 URL 中的原始文件名,避免路径穿越。

缺失图片与下载失败的兜底

  • 占位图:在受控目录准备一张固定的 placeholder.png,失败时改用它,并在正文标注“图片获取失败”。
  • 明确失败:涉及合规或审计的场景不做静默降级,直接抛错并记录失败原因。
  • 分类与日志:区分超时、类型不符、超限、非白名单等原因,至少记录目标域名、失败分类与耗时,便于判断是网络问题还是配置问题。
  • 重试边界:对连接超时等可重试错误做有限次退避重试,对类型不符、超限这类不可重试错误直接失败。

实战说明

验证步骤

  1. 准备一张受支持格式的图片,放到受控目录,记下路径。
  2. 用上方模板执行 executeContent,写入 image-report.pdf。
  3. 打开 PDF,确认出现 200×150 的图片区域,且位置在标题之后。
  4. 把 src 改成一个不存在的路径,观察异常与输出,确认失败路径不会产出无照片的“成功”文件。
  5. 网络图片:先手工下载到受控目录,再复用同一段 <image> 模板渲染,确认“下载”与“渲染”两段边界清晰。

结果预期

  • 本地图片存在时,PDF 中出现指定尺寸的图片。
  • 路径错误时,渲染阶段抛出可定位的错误,而不是静默产出空白页。
  • 网络图片下载成功后,模板侧与本地图片无差别。

常见问题排查

  • 图片不显示:先确认路径是否存在、是否是相对工作目录,再确认模板里 src 的双引号是否被错误转义。
  • 尺寸与预期不符:检查是否只写了 width 或 height,以及单位是否在 px、pt、mm、cm、in 之列。
  • 网络图片失败:按“白名单 → 协议与响应码 → Content-Type → 字节与像素上限 → 超时”的顺序逐项检查。
  • PDF 体积异常:多半来自原图像素过大,而不是 width、height 写大了。

生产注意事项

  • 不要把任意用户 URL 直接交给渲染器;网络访问属于应用层能力,不承诺 http:// 直链可用。
  • 及时清理临时文件,避免磁盘堆积。
  • 注意 PNG 透明背景在不同阅读器中的显示差异,以及高 DPI 原图带来的体积增长,建议统一缩放策略。
  • 适用于商品报告、证书、巡检照片、合同附件等场景。
  • 图片数量多时,控制单次导出的图片数量与单页图片数量,避免 PDF 体积与内存峰值同时失控。

总结

  • 本文的关键结论:模板侧只负责“把受控图片放进 PDF”,网络下载、安全校验、缓存与临时文件清理都应由后端承担。
  • 常见误区:认为 <image src> 可以直接吃网络 URL、把下载失败当成功、跳过 Content-Type 与大小校验、用 URL 原始文件名直接落地。
  • 把资源安全与版面渲染分层,图片类 PDF 才能稳定上线。版本基线:jquick-pdfx 4.0.0、JDK 8+,本文示例基于仓库现有 image sample,升级前请核对 README_zh.md 的版本对照表;更多示例见 GitHub 仓库。
相关文章
|
7天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1311 6
|
5天前
|
人工智能 API 开发工具
DeepSeek Harness 神插件:白嫖 13 家大厂模型
`dsh-our-free-model` 是 DeepSeek Harness 神级插件:零配置、零API Key、零账号,聚合13家大厂免费模型(含DeepSeek V4.1 Flash、Kimi K3等),支持本地网关、用量看板与自动降级,真正实现“白嫖即用”。
|
10天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
1月前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9965 33
|
5天前
|
人工智能 开发者
Qoder CN 大使计划正式开启|无限创造,等你一起
全年免费积分、优先内测、活动支持与更多共创机会,邀请用 Qoder 把想法变成现实的你。
523 6
|
16天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
7天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
747 2
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
15天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
1069 1