使用PDFBox 2.0.17签署具有多个签名字段的PDF文档

3
我正在尝试使用PDFBox提供的示例代码(https://svn.apache.org/repos/asf/pdfbox/trunk/examples/src/main/java/org/apache/pdfbox/examples/signature/CreateVisibleSignature.java)对含有2个签名字段的PDF进行签名。但是签名后的PDF显示为“此文档已被更改,签名无效”。
我已经将我的样例项目上传到了GitHub,请在这里找到。
该项目可以使用IntelliJ或Eclipse打开。
程序参数应设置为以下模拟问题: keystore/lawrence.p12 12345678 pdfs/Fillable-2.pdf images/image.jpg 如果有PDFBox专家能够帮助我,我将不胜感激。谢谢。

首先,请将您的测试代码简化为一个简单的类。测试pdfbox签名不需要使用Spring应用程序。此外,这样的框架可能会带来依赖性,而这些依赖性本身可能会引起问题。 - mkl
亲爱的@mkl,我刚刚发现了问题。Fillable-2.pdf文件中包含两个签名字段(Signature2和Signature4)和两个文本字段(Text1和Text3)。一旦签署了Signature2,Text1应该被锁定,然后当Signature4被签署时,Text3也会被锁定。我想要实现的工作是,person1在Text1填写内容,然后在Signature2处签名,然后传给person2,然后person2在Text3填写内容并在Signature4处签名。当我使用Acrobat Reader时,这个过程是完美的。但是当使用PDFBox时,输出的签名PDF中的“锁定”功能消失了! - Lawrence Leung
关于锁定,这个答案可能会对你有所帮助。 - mkl
使用此答案中的代码,我能够成功签署您的第一个签名字段。 - mkl
亲爱的 @mkl,签署后有没有办法锁定相应的文本框?例如:Text1 - Lawrence Leung
请看我的答案,它根据参考答案改进了代码。 - mkl
1个回答

3
这个回答 解决了在签名后破坏签名的原因——“锁定”签名字段中的字典,并包含了一个遵守锁定字典规则并创建匹配的FieldMDP转换的签名代码。
如一条评论所述,问题的提出者想知道:

在签名后是否有任何方法可以锁定相应的文本字段。

因此,不仅受保护的表单字段的更改会使该签名无效,而且在签名过程中,这些受保护的字段也将被锁定。
事实上,我们可以改进参考答案的代码来实现这一点。
PDSignatureField signatureField = FIND_YOUR_SIGNATURE_FIELD_TO_SIGN;
PDSignature signature = new PDSignature();
signatureField.setValue(signature);

COSBase lock = signatureField.getCOSObject().getDictionaryObject(COS_NAME_LOCK);
if (lock instanceof COSDictionary)
{
    COSDictionary lockDict = (COSDictionary) lock;
    COSDictionary transformParams = new COSDictionary(lockDict);
    transformParams.setItem(COSName.TYPE, COSName.getPDFName("TransformParams"));
    transformParams.setItem(COSName.V, COSName.getPDFName("1.2"));
    transformParams.setDirect(true);
    COSDictionary sigRef = new COSDictionary();
    sigRef.setItem(COSName.TYPE, COSName.getPDFName("SigRef"));
    sigRef.setItem(COSName.getPDFName("TransformParams"), transformParams);
    sigRef.setItem(COSName.getPDFName("TransformMethod"), COSName.getPDFName("FieldMDP"));
    sigRef.setItem(COSName.getPDFName("Data"), document.getDocumentCatalog());
    sigRef.setDirect(true);
    COSArray referenceArray = new COSArray();
    referenceArray.add(sigRef);
    signature.getCOSObject().setItem(COSName.getPDFName("Reference"), referenceArray);

    final Predicate<PDField> shallBeLocked;
    final COSArray fields = lockDict.getCOSArray(COSName.FIELDS);
    final List<String> fieldNames = fields == null ? Collections.emptyList() :
        fields.toList().stream().filter(c -> (c instanceof COSString)).map(s -> ((COSString)s).getString()).collect(Collectors.toList());
    final COSName action = lockDict.getCOSName(COSName.getPDFName("Action"));
    if (action.equals(COSName.getPDFName("Include"))) {
        shallBeLocked = f -> fieldNames.contains(f.getFullyQualifiedName());
    } else if (action.equals(COSName.getPDFName("Exclude"))) {
        shallBeLocked = f -> !fieldNames.contains(f.getFullyQualifiedName());
    } else if (action.equals(COSName.getPDFName("All"))) {
        shallBeLocked = f -> true;
    } else { // unknown action, lock nothing
        shallBeLocked = f -> false;
    }
    lockFields(document.getDocumentCatalog().getAcroForm().getFields(), shallBeLocked);
}

signature.setFilter(PDSignature.FILTER_ADOBE_PPKLITE);
signature.setSubFilter(PDSignature.SUBFILTER_ADBE_PKCS7_DETACHED);
signature.setName("blablabla");
signature.setLocation("blablabla");
signature.setReason("blablabla");
signature.setSignDate(Calendar.getInstance());
document.addSignature(signature [, ...]);

(CreateSignature助手方法signAndLockExistingFieldWithLock)

使用以下实现的lockFields

boolean lockFields(List<PDField> fields, Predicate<PDField> shallBeLocked) {
    boolean isUpdated = false;
    if (fields != null) {
        for (PDField field : fields) {
            boolean isUpdatedField = false;
            if (shallBeLocked.test(field)) {
                field.setFieldFlags(field.getFieldFlags() | 1);
                if (field instanceof PDTerminalField) {
                    for (PDAnnotationWidget widget : ((PDTerminalField)field).getWidgets())
                        widget.setLocked(true);
                }
                isUpdatedField = true;
            }
            if (field instanceof PDNonTerminalField) {
                if (lockFields(((PDNonTerminalField)field).getChildren(), shallBeLocked))
                    isUpdatedField = true;
            }
            if (isUpdatedField) {
                field.getCOSObject().setNeedToBeUpdated(true);
                isUpdated = true;
            }
        }
    }
    return isUpdated;
}

(CreateSignature辅助方法lockFields)


太棒了!非常感谢。我从这个答案中学到了很多东西! - Lawrence Leung

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接