You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于iText Java使用临时文件签名替代内存依赖方案问询

我来帮你解决这个问题——不用把PdfSignatureAppearance存在会话里,改用临时文件完成PKCS7签名的方案其实很直观,核心是把签名过程的中间状态(插入签名外观后的待签PDF)落地到临时文件,后续签名时直接基于这个文件操作,完全不需要保留内存中的对象。

实现方案

整个流程分成两个独立阶段,通过临时文件传递中间状态,彻底摆脱会话存储依赖:

第一步:插入签名外观、生成待签哈希,同时保存临时中间文件

这一步我们不再把PdfSignatureAppearance存到会话,而是把插入签名域后的PDF保存为临时文件,同时记录好后续签名需要的关键参数(签名域名称、预估签名内容长度):

import org.apache.commons.io.IOUtils;
import org.apache.commons.codec.digest.DigestUtils;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.*;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;

// 1. 准备原始PDF文件
File originalFile = new File("To be signed file location");
char version = '\u0000';
int contentEstimated = 16384;
String signatureName = "Signature1";

// 2. 创建临时文件存储插入签名外观后的中间PDF
File tempFile = File.createTempFile("temp_pre_signed_", ".pdf");
tempFile.deleteOnExit(); // 确保JVM退出时自动清理

try (FileOutputStream tempOut = new FileOutputStream(tempFile)) {
    // 插入签名域到PDF
    PdfReader pdfReader = new PdfReader(new FileInputStream(originalFile.getAbsolutePath()));
    PdfStamper stamper = new PdfStamper(pdfReader, tempOut, version, true);
    
    PdfFormField pdfFormField = PdfFormField.createSignature(stamper.getWriter());
    pdfFormField.setWidget(new Rectangle(20f, 20f, 100f, 60f), null);
    pdfFormField.setFlags(4);
    pdfFormField.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g"));
    pdfFormField.setFieldName(signatureName);
    pdfFormField.setPage(1);
    stamper.addAnnotation(pdfFormField, 1);
    
    stamper.close();
    pdfReader.close();
    
    // 3. 生成待签哈希
    pdfReader = new PdfReader(new FileInputStream(tempFile));
    PdfStamper signatureStamper = PdfStamper.createSignature(pdfReader, null, version, null, true);
    PdfSignatureAppearance appearance = signatureStamper.getSignatureAppearance();
    
    // 设置签名外观参数
    appearance.setLayer2Text("Digitally Signed by Name");
    appearance.setImage(Image.getInstance(esignRequestCO.logoLocation));
    appearance.setAcro6Layers(true);
    Calendar cal = Calendar.getInstance();
    cal.setTime(new Date());
    cal.add(Calendar.MINUTE, 5);
    appearance.setSignDate(cal);
    appearance.setVisibleSignature(signatureName);
    
    // 准备签名字典
    HashMap<PdfName, Integer> exc = new HashMap<>();
    exc.put(PdfName.CONTENTS, contentEstimated * 2 + 2);
    PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
    dic.setReason(appearance.getReason());
    dic.setLocation(appearance.getLocation());
    dic.setDate(new PdfDate(appearance.getSignDate()));
    appearance.setCryptoDictionary(dic);
    
    // 预关闭并获取待签数据流
    appearance.preClose(exc);
    InputStream inp = appearance.getRangeStream();
    byte[] bytes = IOUtils.toByteArray(inp);
    String hash = DigestUtils.sha256Hex(bytes);
    
    // 这里不需要保存appearance,只需要记录tempFile的路径、signatureName、contentEstimated
    // 可以把这些参数存在会话或者数据库中(比存大对象轻便得多)
    session.setAttribute("tempFilePath", tempFile.getAbsolutePath());
    session.setAttribute("signatureName", signatureName);
    session.setAttribute("contentEstimated", contentEstimated);
    
    // 把hash返回给客户端进行签名
} catch (Exception e) {
    // 异常处理
    tempFile.delete();
    throw e;
}

第二步:收到客户端签名内容后,基于临时文件完成签名

这一步我们从临时文件重新构建签名环境,不需要依赖之前的PdfSignatureAppearance对象:

import com.itextpdf.text.pdf.*;

import java.io.*;

// 1. 从会话获取之前保存的参数
String tempFilePath = (String) session.getAttribute("tempFilePath");
String signatureName = (String) session.getAttribute("signatureName");
int contentEstimated = (Integer) session.getAttribute("contentEstimated");
byte[] p7barray = "signed content here".getBytes(); // 客户端返回的签名内容

File tempFile = new File(tempFilePath);
String outputFile = "Signed file name";

try (FileOutputStream outputStream = new FileOutputStream(outputFile)) {
    // 2. 基于临时文件创建签名环境
    PdfReader pdfReader = new PdfReader(new FileInputStream(tempFile));
    PdfStamper stamper = PdfStamper.createSignature(pdfReader, outputStream, '\u0000', null, true);
    PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
    
    // 重新设置必要的外观参数(或者可以把这些参数也存在会话里)
    appearance.setLayer2Text("Digitally Signed by Name");
    appearance.setImage(Image.getInstance(esignRequestCO.logoLocation));
    appearance.setAcro6Layers(true);
    appearance.setVisibleSignature(signatureName);
    
    // 3. 填充签名内容
    byte[] paddedSig = new byte[contentEstimated];
    System.arraycopy(p7barray, 0, paddedSig, 0, p7barray.length);
    PdfDictionary dic2 = new PdfDictionary();
    dic2.put(PdfName.CONTENTS, new PdfString(paddedSig).setHexWriting(true));
    
    // 完成签名
    appearance.close(dic2);
    
    pdfReader.close();
} catch (Exception e) {
    // 异常处理
    throw e;
} finally {
    // 清理临时文件
    tempFile.delete();
    // 清理会话中的参数
    session.removeAttribute("tempFilePath");
    session.removeAttribute("signatureName");
    session.removeAttribute("contentEstimated");
}
关键优势
  • 完全摆脱了对PdfSignatureAppearance对象的会话存储依赖,避免了大对象占用内存或序列化问题
  • 临时文件自动清理,不会残留垃圾文件
  • 两个阶段完全解耦,扩展性更强

内容的提问来源于stack exchange,提问作者Prakhar Agrawal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 04:02:55