【问题标题】:Multiple esign using pdfbox 2.0.12 java?使用 pdfbox 2.0.12 java 进行多重设计?
【发布时间】:2018-10-16 06:54:03
【问题描述】:

我试图在压模上的单个 pdf 中添加多个签名。我可以添加多个压模。就我而言,我遇到了错误

至少一个签名无效。 还需要使所有签名有效。

我想在单个 PDF 中添加多个有效标志。请帮我。 图片中只有一个标志有效,其他标志无效,让我看看我做错了什么

下面是我的代码截图

public void getSignOnPdf(Map<Integer, byte[]> PdfSigneture1, List<Long> documentIds, List<String> calTimeStamp,
        String originalPdfReadServerPath, String tickImagePath, int serverTime, int pageNumberToInsertStamp,
        String name, String location, String reasonForSign, int xCo_ordinates, int yCo_ordinates,
        int signatureWidth, int signatureHeight, String pdfPassword, String internal_outputFinalPdfPath)
        throws Exception {
    String pdfReadServerPath = null;
    String l_slash = new String();
    String originalPDFPath = new String(originalPdfReadServerPath.trim());
    
    boolean isCorrectPDFOutputPath = false;
    String aspOutputPdfServerPath = null;
    synchronized (this) {
        if ((internal_outputFinalPdfPath != null) && (!internal_outputFinalPdfPath.trim().isEmpty())) {
            System.out.println("[" + EsignCommonFuntion.generateTimeStampForLog()
                    + "] :1-->  outputFinalPdfPath is: " + internal_outputFinalPdfPath);
            if (!(new File(internal_outputFinalPdfPath)).isFile()) {
                isCorrectPDFOutputPath = true;
                aspOutputPdfServerPath = internal_outputFinalPdfPath;
            } else {
                System.out.println("1--> Please provide directory path for outputFinalPdfPath: "
                        .concat(String.valueOf(internal_outputFinalPdfPath)));
            }
        } else {
            System.out.println(" 1--> outputFinalPdfPath is empty or null: "
                    .concat(String.valueOf(internal_outputFinalPdfPath)));
        }
    }
    boolean isPasswordPresent = false;
    String pdfPasswordForEncryption;
    synchronized (this) {
        if ((pdfPassword != null) && (!pdfPassword.trim().isEmpty())) {
            pdfPasswordForEncryption = pdfPassword.trim();
            isPasswordPresent = true;
        } else {
            pdfPasswordForEncryption = null;
        }
        String pdfOriginalName = (new File(originalPDFPath)).getName();
        String pdfAbsolutePath = originalPDFPath.substring(0, originalPDFPath.lastIndexOf(l_slash));
        if (isPasswordPresent) {
            pdfAbsolutePath = getEncryptedPdfName(originalPDFPath, pdfAbsolutePath + l_slash,
                    pdfPasswordForEncryption, pdfOriginalName);
            pdfReadServerPath = new String(pdfAbsolutePath);
        } else {
            pdfReadServerPath = originalPDFPath;
        }
    }
    ArrayList<String> unSignedFilesList = new ArrayList<String>();

    Map<Integer, byte[]> l_PdfSigneture = PdfSigneture1;

    int actual_pageNumForStamp = 1;

    String pdfFileName = (new File(pdfReadServerPath)).getName();

    FileOutputStream fos = null;

    String nameToShowInSignature = name;
    String locationToShowInSignature = location;
    String reasonForSignatureSign = reasonForSign;

    PDDocument documentFinal = null;
    try {
        pdfReadServerPath = pdfReadServerPath.substring(0, pdfReadServerPath.lastIndexOf(l_slash));
        System.out.println("inside getSignOnMethod pdfAbsolutePath:".concat(String.valueOf(pdfReadServerPath)));
        unSignedFilesList.add(pdfFileName);
        System.out.println("inside getSignOnMethod pdfFileName:".concat(String.valueOf(pdfFileName)));
        
        String PDFpath = pdfReadServerPath + l_slash + (String) (unSignedFilesList).get(0);

        System.out.println("Inside for PDFpath: ".concat(String.valueOf(PDFpath)));

        String finalOutputPdfName = ((String) (unSignedFilesList).get(0)).substring(0,
                ((String) (unSignedFilesList).get(0)).lastIndexOf(".")) + "_signedFinal.pdf";

        File outFile2 = null;

        if (isCorrectPDFOutputPath) {
            System.out.println("if condition Final signed PDF ouptut Path: " + aspOutputPdfServerPath + l_slash
                    + finalOutputPdfName);
            outFile2 = new File(aspOutputPdfServerPath + l_slash + finalOutputPdfName);
            fos = new FileOutputStream(outFile2);
        } else {
            outFile2 = new File(pdfReadServerPath + l_slash + outFile2);
            fos = new FileOutputStream(outFile2);
        }

        documentFinal = PDDocument.load(new File(PDFpath));
            
        for (int i = 1; i < 4; i++) {
            FileInputStream image2 = new FileInputStream(tickImagePath);
            
            PDSignature pdsignature = new PDSignature();
            pdsignature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
            pdsignature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);

            Calendar cal = GregorianCalendar.getInstance();
            SimpleDateFormat l_simpleDateFormater = new SimpleDateFormat("yyyyMMdd_HHmmss");
            String timeStamp = (String) calTimeStamp.get(i - 1);

            try {
                cal.setTime(l_simpleDateFormater.parse(timeStamp));
            } catch (ParseException ex) {
                ex.printStackTrace();
            }

            cal.add(12, serverTime);
            pdsignature.setSignDate(cal);
            documentFinal.setDocumentId((Long) documentIds.get(i - 1));

            String dateToShowInSignature = cal.getTime().toString();
            
            Float saveIncrementalObj1 = null;
            saveIncrementalObj1 = new Float((float) xCo_ordinates, (float) yCo_ordinates, (float) signatureWidth,
                    (float) signatureHeight);

            PDRectangle rect = getPDRectangle(documentFinal, saveIncrementalObj1, i);
            PDVisibleSignDesigner visibleSig;
            (visibleSig = new PDVisibleSignDesigner(documentFinal, image2, i)).xAxis(xCo_ordinates)
                    .yAxis(yCo_ordinates).zoom(-95.0F).signatureFieldName("signature");
            
            PDVisibleSigProperties visibleSignatureProp = new PDVisibleSigProperties();
            
            visibleSignatureProp.signerName("name").signerLocation("location").signatureReason("Security")
                    .preferredSize(0).page(i - 1).visualSignEnabled(true).setPdVisibleSignature(visibleSig)
                    .buildSignature();
            try {
                PdfSigneture = new TreeMap<>();
                // PdfSigneture.clear();
                PdfSigneture = l_PdfSigneture;
                
                if (visibleSignatureProp.isVisualSignEnabled()) {
                    this.options = new SignatureOptions();
                    this.options.setVisualSignature(visibleSignatureProp);
                    this.options.setPage(visibleSignatureProp.getPage());
                    this.options.setVisualSignature(
                            getInputStream(documentFinal, i, rect, tickImagePath, nameToShowInSignature,
                                    locationToShowInSignature, dateToShowInSignature, reasonForSignatureSign));
                    documentFinal.addSignature(pdsignature, this, this.options);
                } else {
                    documentFinal.addSignature(pdsignature, this);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        
        synchronized (this) {
            SaveIncrementalSignObject saveIncrementalSignObject;
            (saveIncrementalSignObject = new SaveIncrementalSignObject()).setFos(fos);
            saveIncrementalSignObject.setPDDocumentFromFile(documentFinal);
            
            saveIncrementalForSign(saveIncrementalSignObject);
        }
    } catch (Exception localException2) {
        System.out.println("Insidemethod -- Exception block" + localException2.getMessage());
        return;
    } finally {
        fos.flush();
        if (fos != null) {
            fos.close();
        }
        documentFinal.close();
    }
}

public static synchronized void saveIncrementalForSign(SaveIncrementalSignObject p_SaveIncrementalObj) {
    PDDocument documentFinal = null;
    try {
        (documentFinal = p_SaveIncrementalObj.getPDDocumentFromFile())
                .saveIncremental(p_SaveIncrementalObj.getFos());
    } catch (Exception e) {
        e.printStackTrace();
        try {
        //              documentFinal.close();
            return;
        } catch (Exception eX) {
            eX.printStackTrace();
            return;
        }
    }
}

【问题讨论】:

  • 请提到PDFBox版本,添加saveIncrementalForSign的代码,以及签名的代码。如果可能,分享签名的 PDF。
  • 是的,我添加了 saveincremental 代码,我的 pdf box 版本是 2.0.12
  • 请同时分享一个样本签名的 PDF。代码太多,很可能首先分析 PDF 以了解问题所在。
  • 谢谢!检查 PDF 时,问题立即显而易见。
  • 您最近的编辑并不是真正的改进。

标签: java pdf pdfbox


【解决方案1】:

您在评论中阐明了您想要实现的目标:

我尝试将一个签名应用于多个位置。

正如下面第一部分所讨论的,这不是您的代码所做的:您的代码尝试将多个签名应用到一个位置在一个单个修订中 这也是不可能的。

另一方面,在单个修订中将单个签名应用到多个地方,这并不是PDF规范团队所希望的,并且规范已使一些实现此功能的方法无效,但如以下第二节所述,这是可能的。

你的方法,以及它为什么行不通

您似乎试图一次应用多个签名:

if (isPasswordPresent) {
    documentFinal = PDDocument.load(new File(PDFpath), pdfPasswordForEncryption);
} else {
    documentFinal = PDDocument.load(new File(PDFpath));
}

for (int i = 1; i < 4; i++) {
    FileInputStream image2 = new FileInputStream(tickImagePath);

    PDSignature pdsignature = new PDSignature();

    [...]

    try {
        [...]

        if (visibleSignatureProp.isVisualSignEnabled()) {
            [...]
            documentFinal.addSignature(pdsignature, this, this.options);
        } else {
            documentFinal.addSignature(pdsignature, this);
        }
    } catch (Exception e) {
        System.out.println("Inside getSignOnPdf sub exception block at addSignature:" + e + "error :" + e.getMessage());
        e.printStackTrace();
    }
}

synchronized (this) {
    [...]
    saveIncrementalForSign(saveIncrementalSignObject);
}

这行不通。

在 PDF 中,多个签名在单独的 PDF 修订版中一个接一个地应用,而不是在同一修订版中并行:

您可以在this answer 中找到一些背景以及从那里引用的文档。

因此,在伪代码中,您需要做的是:

for (int i = 1; i < 4; i++) {
    load current version of the PDF;
    apply the i'th signature;
    save and sign as new current version of the PDF;
}

方法名称PDDocument.addSignature 在这里可能有点误导,因为它可能被认为暗示可以添加多个签名。不是这种情况;所有签名都将创建为带有其小部件的签名字段,但实际上只有最后添加的 PDSignature 的字段会被签名,因此只有最后添加的签名字段才会真正具有合理的值。

@Tilman - 如果在加载文档后已经添加了签名,则可能应该在 PDDocument.addSignature 中进行测试并抛出异常。

讨论你的实际任务

PDF 对象从 PDF 页面上的签名可视化到实际签名(CMS 签名容器在基于 CMS 的子过滤器的情况下)的路径不是即时的。相反,我们有

  • PDF 页面,在其注释中引用
  • 签名字段小部件(签名可视化)属于
  • 签名字段引用
  • 嵌入 CMS 签名容器的签名值字典。

为了执行你的实际任务,

将一个签名应用于多个位置,

因此,似乎有许多选项可以从具有签名外观的多个页面获取到单个签名容器:

  1. 具有签名可视化的所有页面都指向单个签名字段的相同单个小部件注释,其值字典包含签名容器。
  2. 每个页面的签名可视化都指向自己的小部件,但所有小部件都属于同一个签名字段,其值字典包含签名容器。
  3. 每个带有签名可视化的页面都指向它们自己的小部件,每个小部件都属于一个单独的签名字段,但它们都指向包含签名容器的同一个值字典。

现在让我们看看 PDF 规范 ISO 32000-2。首先,它警告不要使用多个可视化的单个签名:

签名在文档中的位置可能会影响其法律意义。 [...]

如果多个位置与签名相关联,则含义可能会变得不明确。

(ISO 32000-2,第 12.7.5.5 节“签名字段”)

因此,规范试图禁止具有多个可视化的单个签名:

一个给定的注释字典应该从只有一页的Annots数组中引用。

(ISO 32000-2,第 12.5.2 节“注释字典”)

这禁止上面的选项 1。

签名字段不得引用多个注释

(ISO 32000-2,第 12.7.5.5 节“签名字段”)

这禁止选项 2。

但显然,选项 3 并未明确禁止。对于通用表单字段值对象共享甚至是明确允许的,因为表单字段值是可继承的!

因此,严格来说,使用选项 3 创建具有多个可视化的签名是可能的。

但请注意,PDF 规范团队显然无意允许他们这样做,这很可能是疏忽。因此,您必须考虑到即将对规范进行的一些更正最终也会禁止选项 3。

如果您仍然想尝试,应该可以调整或修补 PDFBox 以使用选项 3 的方法创建具有多个可视化的单个签名。

这已经被证明是可行的,例如iText,参见。 this answer.

此外,您共享的示例文档使用了此选项。

概念证明

事实证明,使用 PDFBox 沿着选项 3 的路线创建多可视化 PDF 签名非常容易。特别是它比使用 iText 更容易,参见。 the answer referenced above,因为这里的签名值字典是一个自己创建和处理的对象,而在 iText 中它是在后台及时创建的。

只需创建一个PDSignature 对象并正常生成一个签名(使用PDDocument.addSignature),然后添加任意数量的其他签名字段,将这些字段的签名值属性设置为单个 PDSignature 对象在开始时创建。

例如您可以使用这样的方法来添加额外的签名字段:

void addSignatureField(PDDocument pdDocument, PDPage pdPage, PDRectangle rectangle, PDSignature signature) throws IOException {
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    List<PDField> acroFormFields = acroForm.getFields();

    PDSignatureField signatureField = new PDSignatureField(acroForm);
    signatureField.setSignature(signature);
    PDAnnotationWidget widget = signatureField.getWidgets().get(0);
    acroFormFields.add(signatureField);

    widget.setRectangle(rectangle);
    widget.setPage(pdPage);

    // from PDVisualSigBuilder.createHolderForm()
    PDStream stream = new PDStream(pdDocument);
    PDFormXObject form = new PDFormXObject(stream);
    PDResources res = new PDResources();
    form.setResources(res);
    form.setFormType(1);
    PDRectangle bbox = new PDRectangle(rectangle.getWidth(), rectangle.getHeight());
    float height = bbox.getHeight();

    form.setBBox(bbox);
    PDFont font = PDType1Font.HELVETICA_BOLD;

    // from PDVisualSigBuilder.createAppearanceDictionary()
    PDAppearanceDictionary appearance = new PDAppearanceDictionary();
    appearance.getCOSObject().setDirect(true);
    PDAppearanceStream appearanceStream = new PDAppearanceStream(form.getCOSObject());
    appearance.setNormalAppearance(appearanceStream);
    widget.setAppearance(appearance);

    try (PDPageContentStream cs = new PDPageContentStream(pdDocument, appearanceStream))
    {
        // show background (just for debugging, to see the rect size + position)
        cs.setNonStrokingColor(Color.yellow);
        cs.addRect(-5000, -5000, 10000, 10000);
        cs.fill();

        float fontSize = 10;
        float leading = fontSize * 1.5f;
        cs.beginText();
        cs.setFont(font, fontSize);
        cs.setNonStrokingColor(Color.black);
        cs.newLineAtOffset(fontSize, height - leading);
        cs.setLeading(leading);
        cs.showText("Signature text");
        cs.newLine();
        cs.showText("some additional Information");
        cs.newLine();
        cs.showText("let's keep talking");
        cs.endText();
    }

    pdPage.getAnnotations().add(widget);
    
    COSDictionary pageTreeObject = pdPage.getCOSObject(); 
    while (pageTreeObject != null) {
        pageTreeObject.setNeedToBeUpdated(true);
        pageTreeObject = (COSDictionary) pageTreeObject.getDictionaryObject(COSName.PARENT);
    }
}

(CreateMultipleVisualizations 辅助方法)

(此方法实际上基于 pdfbox 示例工件中的 CreateVisibleSignature2.createVisualSignatureTemplate 方法,但经过了极大的简化,现在用于创建实际的签名字段,而不仅仅是用于复制的模板。)

这样使用

try (   InputStream resource = PDF_SOURCE_STREAM;
        OutputStream result = PDF_TARGET_STREAM;
        PDDocument pdDocument = PDDocument.load(resource)   )
{
    PDAcroForm acroForm = pdDocument.getDocumentCatalog().getAcroForm();
    if (acroForm == null) {
        pdDocument.getDocumentCatalog().setAcroForm(acroForm = new PDAcroForm(pdDocument));
    }
    acroForm.setSignaturesExist(true);
    acroForm.setAppendOnly(true);
    acroForm.getCOSObject().setDirect(true);

    PDRectangle rectangle = new PDRectangle(100, 600, 300, 100);
    PDSignature signature = new PDSignature();
    signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
    signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
    signature.setName("Example User");
    signature.setLocation("Los Angeles, CA");
    signature.setReason("Testing");
    signature.setSignDate(Calendar.getInstance());
    pdDocument.addSignature(signature, this);

    for (PDPage pdPage : pdDocument.getPages()) {
        addSignatureField(pdDocument, pdPage, rectangle, signature);
    }

    pdDocument.saveIncremental(result);
}

(CreateMultipleVisualizations 测试testCreateSignatureWithMultipleVisualizations)

在结果文档的每一页上检索一个带有签名可视化的 PDF(以及一个额外的不可见的,因为我有点懒),但只有一个实际签名值(假设 this 使用 SignatureInterface 实现byte[] sign(InputStream) 方法)。

但请注意:

  • PDSignatureField 方法 setSignature 已在 PDFBox 3.0.0-SNAPSHOT 中弃用。您最终可能不得不使用更底层的技术注入 PDSignature 对象。
  • PDF 规范团队不需要这种多可视化签名。很有可能它们最终会被禁止。

【讨论】:

  • 我尝试将一个签名应用于多个位置。有可能吗??
  • "hi mkl wishify.in/signed_one.pdf" - 该 PDF 具有混合交叉引用结构(MS Word 样式)。请首先尝试使用仅包含交叉引用表或仅包含交叉引用流的不同 PDF。这些混合体会使 PDFBox 无法正常工作。
  • “我尝试将一个签名应用于多个位置。这可能吗??” - PDF 规范团队不希望这样做。有些技巧在 PDF 1.x 中有效,但在 PDF 2.0 中被禁止。你不应该那样做。
  • @Rahul 如前所述,每个修订版只能有一个签名。您的输出图像可能是应用了 pdf 2 中很可能不再允许的技巧的结果。如需进一步分析,请分享 pdf。
  • 该链接对您没有多大帮助,@mkl 是为了表明他的建议已付诸实施。 2.0.13 中的修复是,如果您尝试调用 addSignature 两次,您将得到一个异常,并且 javadoc 也警告不要这样做。我看了你的PDF……签名内容是一样的,当然是不正确的,是你多次签名的结果。所以请只签一次。如果您想多次签名,则保存增量、关闭、重新打开已签名的 PDF 等。
猜你喜欢
  • 1970-01-01
  • 2017-11-23
  • 1970-01-01
  • 2019-03-05
  • 2014-12-03
  • 1970-01-01
  • 2020-09-10
  • 2016-12-01
  • 1970-01-01
相关资源
最近更新 更多