All Downloads are FREE. Search and download functionalities are using the official Maven repository.

wiki.xsx.core.pdf.doc.XEasyPdfDocumentSigner Maven / Gradle / Ivy

package wiki.xsx.core.pdf.doc;

import lombok.SneakyThrows;
import org.apache.pdfbox.cos.COSArray;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.PDSignature;
import org.apache.pdfbox.pdmodel.interactive.digitalsignature.SignatureInterface;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;

import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.List;

/**
 * pdf文档签名器
 *
 * @author xsx
 * @date 2021/12/7
 * @since 1.8
 * 

* Copyright (c) 2020-2023 xsx All Rights Reserved. * x-easypdf is licensed under Mulan PSL v2. * You can use this software according to the terms and conditions of the Mulan PSL v2. * You may obtain a copy of Mulan PSL v2 at: * http://license.coscl.org.cn/MulanPSL2 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. * See the Mulan PSL v2 for more details. *

*/ public class XEasyPdfDocumentSigner implements Serializable { private static final long serialVersionUID = -3449241065190072431L; /** * pdf文档签名器参数 */ private final XEasyPdfDocumentSignerParam param = new XEasyPdfDocumentSignerParam(); /** * 有参构造 * * @param pdfDocument pdf文档 */ XEasyPdfDocumentSigner(XEasyPdfDocument pdfDocument) { this.param.setPdfDocument(pdfDocument); this.param.setDocument(this.param.getPdfDocument().build(true)); } /** * 有参构造 * * @param pdfDocument pdf文档 */ XEasyPdfDocumentSigner(XEasyPdfDocument pdfDocument, PDDocument document) { this.param.setPdfDocument(pdfDocument); this.param.setDocument(document); } /** * 设置签名信息 * * @param name 名称 * @param location 位置 * @param reason 原因 * @param contactInfo 信息 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setSignerInfo( String name, String location, String reason, String contactInfo ) { this.param.getSignature().setName(name); this.param.getSignature().setLocation(location); this.param.getSignature().setReason(reason); this.param.getSignature().setContactInfo(contactInfo); return this; } /** * 设置签名过滤器 * * @param filter 过滤器 * @param subFilter 子过滤器 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setSignFilter( XEasyPdfDocumentSignFilter.Filter filter, XEasyPdfDocumentSignFilter.SubFilter subFilter ) { if (filter != null) { this.param.getSignature().setFilter(filter.getFilter()); } if (subFilter != null) { this.param.getSignature().setSubFilter(subFilter.getFilter()); } return this; } /** * 设置签名图片 * * @param image 图片 * @param marginLeft 图片左边距 * @param marginTop 图片上边距 * @param scalePercent 图片缩放比例 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setSignImage( BufferedImage image, float marginLeft, float marginTop, float scalePercent ) { this.param.setImage(image) .setImageMarginLeft(marginLeft) .setImageMarginTop(marginTop) .setImageScalePercent(scalePercent - 100); return this; } /** * 设置签名证书 * * @param signAlgorithm 签名算法 * @param keyStoreType 密钥库类型 * @param certificate 证书文件 * @param certificatePassword 证书密码 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setCertificate( XEasyPdfDocumentSignAlgorithm signAlgorithm, XEasyPdfDocumentSignKeyStoreType keyStoreType, File certificate, String certificatePassword ) { this.param.setSignAlgorithm(signAlgorithm) .setKeyStoreType(keyStoreType) .setCertificate(certificate) .setCertificatePassword(certificatePassword); return this; } /** * 设置签名内存大小(默认:250K) * * @param preferredSignatureSize 签名内存大小 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setPreferredSignatureSize(int preferredSignatureSize) { this.param.setPreferredSignatureSize(preferredSignatureSize); return this; } /** * 设置自定义签名接口 * * @param customSignature 自定义pdfbox签名接口 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setCustomSignature(SignatureInterface customSignature) { this.param.setCustomSignature(customSignature); return this; } /** * 设置签名后pdf权限 * * @param accessPermissions pdf权限 * @return 返回pdf文档签名器 */ public XEasyPdfDocumentSigner setAccessPermissions(int accessPermissions) { this.param.setAccessPermissions(accessPermissions); return this; } /** * 签名 * * @param pageIndex 签名页面索引 * @param outputStream 输出流 */ @SneakyThrows public void sign(int pageIndex, OutputStream outputStream) { // 创建字节数组输出流 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8192)) { // 初始化参数 this.param.init(pageIndex); // 创建任务文档 PDDocument target = this.param.getDocument(); // 保存文档 target.save(byteArrayOutputStream); // 添加签名 this.addSignature(true, PDDocument.load(byteArrayOutputStream.toByteArray()), outputStream); } // 关闭文档 this.param.getPdfDocument().close(); } /** * 签名 * * @param outputStream 输出流 * @param pageIndexes 签名页面索引 */ @SneakyThrows public void sign(OutputStream outputStream, int... pageIndexes) { // 创建字节数组输出流 try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8192)) { // 获取任务文档 PDDocument target = this.param.getDocument(); // 保存文档 target.save(byteArrayOutputStream); // 关闭文档 target.close(); // 重置任务文档 this.param.setDocument(PDDocument.load(byteArrayOutputStream.toByteArray())); } // 定义当前索引 int index = 0; // 获取签名内存大小(默认:250K) int signSize = this.param.getPreferredSignatureSize(); // 获取pdf访问权限 int accessPermission = this.param.getAccessPermissions(); // 如果页面索引不为空,则根据页面索引添加签名 if (pageIndexes != null && pageIndexes.length > 0) { // 获取最后索引 int lastIndex = pageIndexes.length - 1; // 遍历页面索引 for (int pageIndex : pageIndexes) { // 添加签名并获取索引 index = this.addSignature(index, pageIndex, index == lastIndex, signSize, accessPermission, this.param.getDocument(), outputStream); // 重置签名内存大小(原大小3.5倍) signSize = (int) (signSize * 3.5); } } // 否则全文档页面签名 else { // 获取页面总数 int total = this.param.getDocument().getNumberOfPages(); // 获取最后索引 int lastIndex = total - 1; // 遍历页面 for (int pageIndex = 0; pageIndex < total; pageIndex++) { // 添加签名并获取索引 index = this.addSignature(index, pageIndex, index == lastIndex, signSize, accessPermission, this.param.getDocument(), outputStream); // 重置签名内存大小(原大小3.5倍) signSize = (int) (signSize * 3.5); } } // 资源释放 this.release(); } /** * 添加签名 * * @param isAddInfo 是否添加信息 * @param target 目标文档 * @param outputStream 输出流 */ @SneakyThrows void addSignature(boolean isAddInfo, PDDocument target, OutputStream outputStream) { // 获取pdf文档 XEasyPdfDocument pdfDocument = this.param.getPdfDocument(); // 添加信息 if (isAddInfo) { // 替换总页码占位符 pdfDocument.replaceTotalPagePlaceholder(target, false); // 设置基础信息(文档信息、保护策略、版本、xmp信息及书签) pdfDocument.setBasicInfo(target); } // 设置mdp权限 this.setMdpPermission(target, this.param.getSignature(), this.param.getAccessPermissions()); // 重置签名表单 this.resetSignForm(target); // 添加签名 target.addSignature(this.param.getSignature(), this.getSignatureInterface(), this.param.getSignatureOptions()); // 保存文档 target.saveIncremental(outputStream); // 关闭文档 target.close(); } /** * 重置签名表单 * * @param target 目标文档 */ void resetSignForm(PDDocument target) { // 获取pdfbox表单 PDAcroForm acroForm = target.getDocumentCatalog().getAcroForm(null); // 如果表单不为空且首次使用,则清除首次使用项 if (acroForm != null && acroForm.getNeedAppearances()) { // 如果表单字段为空,则清除首次使用项 if (acroForm.getFields().isEmpty()) { // 清除首次使用项 acroForm.getCOSObject().removeItem(COSName.NEED_APPEARANCES); } } } /** * 获取签名参数 * * @return 返回签名参数 */ XEasyPdfDocumentSignerParam getParam() { return this.param; } /** * 添加签名 * * @param index 当前索引 * @param pageIndex 页面索引 * @param isLast 是否最后一页 * @param signSize 签名内存 * @param accessPermission 权限 * @param target pdfbox文档 * @param outputStream 输出流 * @return 返回索引 */ @SneakyThrows private int addSignature( int index, int pageIndex, boolean isLast, int signSize, int accessPermission, PDDocument target, OutputStream outputStream ) { // 设置签名内存大小 this.param.setPreferredSignatureSize(signSize); // 如果为最后签名,则保存为最后文档 if (isLast) { // 设置访问权限 this.param.setAccessPermissions(accessPermission); // 初始化参数 this.param.init(pageIndex); // 添加签名 this.addSignature(true, target, outputStream); } // 否则保存临时文档 else { // 设置访问权限 this.param.setAccessPermissions(accessPermission > 1 ? 2 : accessPermission); // 初始化参数 this.param.init(pageIndex); // 获取临时目录 String tempPath = this.param.getTempDir() + File.separatorChar + index; // 创建文件流 try (FileOutputStream fileOutputStream = new FileOutputStream(tempPath)) { // 添加签名 this.addSignature(false, target, fileOutputStream); // 创建新输入流 try (InputStream inputStream = Files.newInputStream(Paths.get(tempPath), StandardOpenOption.DELETE_ON_CLOSE)) { // 设置pdfbox文档 this.param.setDocument(PDDocument.load(inputStream)); } } } // 返回当前索引+1 return index + 1; } /** * 获取签名接口 * * @return 返回签名接口 */ @SneakyThrows private SignatureInterface getSignatureInterface() { return this.param.getCustomSignature() != null ? this.param.getCustomSignature() : new XEasyPdfDocumentSignDefaultProcessor(this); } /** * 获取mdp权限 * * @param doc pdfbox文档 * @return 返回mdp权限 */ private int getMdpPermission(PDDocument doc) { COSBase base = doc.getDocumentCatalog().getCOSObject().getDictionaryObject(COSName.PERMS); if (base instanceof COSDictionary) { COSDictionary permsDict = (COSDictionary) base; base = permsDict.getDictionaryObject(COSName.DOCMDP); if (base instanceof COSDictionary) { COSDictionary signatureDict = (COSDictionary) base; base = signatureDict.getDictionaryObject("Reference"); if (base instanceof COSArray) { COSArray refArray = (COSArray) base; for (int i = 0; i < refArray.size(); ++i) { base = refArray.getObject(i); if (base instanceof COSDictionary) { COSDictionary sigRefDict = (COSDictionary) base; if (COSName.DOCMDP.equals(sigRefDict.getDictionaryObject("TransformMethod"))) { base = sigRefDict.getDictionaryObject("TransformParams"); if (base instanceof COSDictionary) { COSDictionary transformDict = (COSDictionary) base; int accessPermissions = transformDict.getInt(COSName.P, 2); if (accessPermissions < 1 || accessPermissions > 3) { accessPermissions = 2; } return accessPermissions; } } } } } } } return 0; } /** * 设置mdp权限 * * @param doc pdfbox文档 * @param signature pdfbox签名 * @param accessPermissions 签名后pdf权限 */ @SneakyThrows private void setMdpPermission(PDDocument doc, PDSignature signature, int accessPermissions) { List signatureList = doc.getSignatureDictionaries(); for (PDSignature sig : signatureList) { if (COSName.DOC_TIME_STAMP.equals(sig.getCOSObject().getItem(COSName.TYPE))) { continue; } if (sig.getCOSObject().containsKey(COSName.CONTENTS)) { return; // throw new IOException("DocMDP transform method not allowed if an approval signature exists"); } } COSDictionary sigDict = signature.getCOSObject(); // DocMDP specific stuff COSDictionary transformParameters = new COSDictionary(); transformParameters.setItem(COSName.TYPE, COSName.getPDFName("TransformParams")); transformParameters.setInt(COSName.P, accessPermissions); transformParameters.setName(COSName.V, "1.2"); transformParameters.setNeedToBeUpdated(true); COSDictionary referenceDict = new COSDictionary(); referenceDict.setItem(COSName.TYPE, COSName.getPDFName("SigRef")); referenceDict.setItem("TransformMethod", COSName.DOCMDP); referenceDict.setItem("DigestMethod", COSName.getPDFName("SHA1")); referenceDict.setItem("TransformParams", transformParameters); referenceDict.setNeedToBeUpdated(true); COSArray referenceArray = new COSArray(); referenceArray.add(referenceDict); sigDict.setItem("Reference", referenceArray); referenceArray.setNeedToBeUpdated(true); // Catalog COSDictionary catalogDict = doc.getDocumentCatalog().getCOSObject(); COSDictionary permsDict = new COSDictionary(); catalogDict.setItem(COSName.PERMS, permsDict); permsDict.setItem(COSName.DOCMDP, signature); catalogDict.setNeedToBeUpdated(true); permsDict.setNeedToBeUpdated(true); } /** * 资源释放 */ @SneakyThrows private void release() { // 关闭文档 this.param.getDocument().close(); // 关闭文档 this.param.getPdfDocument().close(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy