基于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
相关产品推荐
相关产品推荐

