# 表单签名字段
签名字段是 PDF 文档中特殊的表单字段类型,专用于电子签名的添加与管理。本章将详细介绍如何使用 Foxit PDF SDK for Web 处理表单签名字段,包括添加、编辑、验证签名,以及对签名过程和用户界面(UI)的自定义。
针对签名功能,Foxit PDF SDK for Web 提供以下支持:
- 创建、编辑、删除签名域和字段
- 签名和验证签名
- 自定义签署人配置信息和签名处理程序
- 自定义验签处理程序
- 自定义签名相关的 UI,包括签名对话框、签名验证对话框和签名信息对话框
# 在 PDF 文档中添加、编辑和删除签名字段
# 创建签名字段
使用 PDFForm
(opens new window) 类的 createSignature
(opens new window) 方法可以向 PDF 文档添加新的签名字段。该方法需要传入一个 CreateSignatureOptions
(opens new window) 对象,用于配置签名字段的属性。
const options = {
pdfRect: {left: 100, top: 100, right: 300, bottom: 200}, // 设置签名字段的位置和大小
pageIndex: 0, // 指定签名字段添加到的页面索引
rotate: 0, // 可选,设置 Widget 的旋转角度,可选值为 0、1、2 或 3,分别表示 0°、90°、180° 和 270°
fieldName: 'SignatureField1' // 可选,指定签名字段的名称;若未指定,将自动生成一个唯一的字段名
};
pdfForm.createSignature(options).then(widget => {
console.log("签名字段已成功创建:", widget);
}).catch(error => {
console.error("创建签名字段时发生错误:", error);
});
签名字段创建成功后,Foxit PDF SDK for Web 会在页面中插入一个新的签名域,并触发 ViewerEvents.annotationAdded
(opens new window) 事件。在处理该事件时,可通过 Annot.getType
(opens new window) 和 PDFFormField.getType
(opens new window) 接口判断是否添加了签名域。
pdfui.addViewerEventListener(PDFViewCtrl.ViewerEvents.annotationAdded, (annotations) => {
const signatureWidgets = annotations.filter(it => {
return it.getType() === PDF.annots.constant.Annot_Type.widget && it.getField().getType() === PDF.form.constant.FieldType.signature
});
// 使用 signatureWidgets 进行后续操作
})
# 删除签名
这里的删除签名是指删除签名域,而非直接删除签名字段。删除签名域的方式与删除注释(Annot)的方法相同,均通过 PDFPage.removeAnnotByObjectNumber
(opens new window) 方法实现。
pdfPage.removeAnnotByObjectNumber(widget.getObjectNumber()).then((removedAnnots) => {
if(removedAnnots.length === 1) {
console.log("签名域已成功删除");
} else {
console.log("未找到要删除的签名域");
}
}, reason => {
console.error("删除签名域时发生错误:", reason);
});
签名域被成功删除后,会返回一个包含已删除签名域的数组。如果未找到要删除的签名域,则返回一个空数组。因此,在删除操作后需要判断返回的数组长度是否为 1 来确认删除是否成功。
此外,签名域删除成功后,如果其所属的表单字段不再包含其他表单域,该字段会被自动清除,并且会触发 DataEvents.formFieldRemoved
(opens new window) 事件。
如果您不熟悉表单字段和表单域的关系,可以参考 表单基础概念介绍 章节。
# 开始签名
签名字段创建成功后,可以通过调用 PDFDoc.sign
(opens new window) 方法进行签名。签名相关的信息(如签名者身份、签名原因、签名位置等)可以在 signInfo 对象中进行详细定义。
const signInfo = {
filter: 'Adobe.PPKLite',
subfilter: 'adbe.pkcs7.sha1',
flag: 0x1f0,
distinguishName: '[email protected]',
location: 'bj',
reason: 'TestBJ',
signer: 'web sdk11',
showTime: true,
signTime: Date.now(),
defaultContentsLength: 7942,
image: 'data:image/png;base64,iVBORw0...',
rotation: 90,
timeFormat: {
format: 'YYYY-MM-DD HH:mm:ss Z',
timeZoneOptions: { prefix: 'GMT' }
},
};
// 示例的签名服务地址
const signature_server = 'https://webviewer-demo.foxitsoftware.com/signature'; // 或者使用您自己部署的签名服务地址
const signedDocBlobData = await pdfDoc.sign(signInfo, async function sign(signInfo, plainContent) {
const formdata = new FormData();
formdata.append("plain", new Blob([plainContent]), "plain");
const response = await fetch(signature_server + '/digest_and_sign', {
method: 'POST',
body: formdata
});
return response.arrayBuffer();
});
在签名过程中,Foxit PDF SDK for Web 会调用 sign
回调函数,将签名相关的数据提交至签名服务。签名服务完成签署后,会返回签名后的数据。
关于签名服务的实现,Foxit PDF SDK for Web 提供了 Windows 和 Linux 系统的示例实现。您可以参考发布包中的 /server/signature-server-for-linux/README.md
和 /server/signature-server-for-win/readme.md
两个说明文件。
为了方便您快速验证签名功能,我们提供了如下的测试服务:
美区服务:
- http://webviewer-demo.foxitsoftware.com/signature/digest_and_sign (opens new window)
- http://webviewer-demo.foxitsoftware.com/signature/verify (opens new window)
- https://webviewer-demo.foxitsoftware.com/signature/digest_and_sign (opens new window)
- https://webviewer-demo.foxitsoftware.com/signature/verify (opens new window)
中国区服务:
注意:以上服务仅用于测试使用,请勿在生产环境中使用。如果我们提供的 signature-server-for-linux
和 signature-server-for-win
的 Node.js 实现无法满足您的需求,您可以参考其源码,并根据需要使用其他技术栈重新实现,以确保其在生产环境中的稳定性和可用性。
签名成功后,返回的 blob 对象包含签名后的 PDF 文件。您可以选择将该文件保存到本地,或者直接通过 PDFViewer.openPDFByFile
(opens new window) 方法打开:
pdfViewer.openPDFByFile(signedDocBlobData, { fileName: 'signed.pdf' });
# 验证签名
# 验签 API
签名成功后,为确保签名字段的有效性,可以使用 PDFSignature.verify
(opens new window) 方法进行验证。
const signatureField = form.getField("Signature field name");
// 示例的签名服务地址
const signature_server = 'https://webviewer-demo.foxitsoftware.com/signature'; // 或者使用您自己部署的签名服务地址
const result = await signatureField.verify({
force: false,
handler: async (signatureField, plainContent, signedData, hasDataOutOfScope) => {
const filter = await signatureField.getFilter();
const subfilter = await signatureField.getSubfilter();
const signer = await signatureField.getSigner();
const formdata = new FormData();
formdata.append("filter", filter);
formdata.append("subfilter", subfilter);
formdata.append("signer", signer);
formdata.append("plainContent", new Blob([plainContent]), "plainContent");
formdata.append("signedData", new Blob([signedData]), "signedData");
const response = await fetch(signature_server + '/verify', {
method: 'POST',
body: formdata
});
const result = parseInt(await response.text());
return result;
}
});
验签完成后,将返回一个 result
值,其类型为 Signature_State (opens new window),用于表示签名的验证结果:
签名验证状态:
verifyValid (4)
: 签名验证状态有效verifyInvalid (8)
: 签名验证状态无效verifyErrorByteRange (64)
: 非预期的字节范围verifyUnknown (2147483648)
: 未知签名
文档更改状态:
verifyChange (128)
: 文档已在签名范围内更改(签名无效)verifyIncredible (256)
: 签名不可信任(包含风险)verifyNoChange (1024)
: 文档未在签名范围内进行更改verifyChangeLegal (134217728)
: 文档已在签名范围之外进行了更改,但这种更改是允许的verifyChangeIllegal (268435456)
: 文档已在签名范围之外进行了更改,且所做的更改使签名无效
颁发者验证状态:
verifyIssueUnknown (2048)
: 颁发者的验证状态未知verifyIssueValid (4096)
: 颁发者的验证状态有效verifyIssueRevoke (16384)
: 验证颁发者的证书已吊销verifyIssueExpire (32768)
: 验证颁发者的证书已过期verifyIssueUncheck (65536)
: 未检查颁发者verifyIssueCurrent (131072)
: 已验证的颁发者是当前颁发者
时间戳状态:
verifyTimestampNone (262144)
: 没有时间戳或未检查时间戳verifyTimestampDoc (524288)
: 该签名是一个时间戳签名verifyTimestampValid (1048576)
: 时间戳的验证状态有效verifyTimestampInvalid (2097152)
: 时间戳的验证状态无效verifyTimestampExpire (4194304)
: 时间戳的验证状态已过期verifyTimestampIssueUnknown (8388608)
: 时间戳颁发者的验证状态未知verifyTimestampIssueValid (16777216)
: 时间戳颁发者的验证状态有效verifyTimestampTimeBefore (33554432)
: 时间戳时间的验证状态有效,因为时间在过期日期之前
注意:在验证签名前,请确认签名字段是否已签署。如果未签署,调用 verify
方法将会报错,因此需先检查签名字段的签署状态。
const isSigned = await signatureField.isSigned();
if (isSigned) {
// 在这里进行验签
}
# 自定义验证处理器(verify handler)
验证处理器是一个异步函数,用于将数据提交到签名验证服务并返回验证结果。可以通过调用 PDFViewer.getSignatureService
(opens new window) 方法获取签名管理服务,并设置一个全局默认的验证处理器。
注意:一旦设置全局默认的验证处理器,Foxit PDF SDK for Web 会自动使用该处理器。在应用层调用 PDFSignature.verify
(opens new window) 方法时,如果未显式指定验证处理器(handler)参数,也会自动使用自定义验证处理器。
const signatureService = pdfViewer.getSignatureService();
// 示例的签名服务地址
const signature_server = 'https://webviewer-demo.foxitsoftware.com/signature'; // 或者使用您自己部署的签名服务地址
signatureService.setVerifyHandler(
async (signatureField, plainContent, signedData, hasDataOutOfScope) => {
const filter = await signatureField.getFilter();
const subfilter = await signatureField.getSubfilter();
const signer = await signatureField.getSigner();
const formdata = new FormData();
formdata.append("filter", filter);
formdata.append("subfilter", subfilter);
formdata.append("signer", signer);
formdata.append("plainContent", new Blob([plainContent]), "plainContent");
formdata.append("signedData", new Blob([signedData]), "signedData");
const response = await fetch(signature_server + '/verify', {
method: 'POST',
body: formdata
});
return parseInt(await response.text());
}
)
# 获取签名信息
PDFSignature.getSignInfo
(opens new window) 方法用于获取签名信息。如果签名已签署,则返回 SignedFormSignatureInfo
(opens new window) 对象, 反之返回 UnsignedFormSignatureInfo
(opens new window) 对象。
# 自定义签名 UI
Foxit PDF SDK for Web 提供了一组接口,用于自定义签名相关的 UI 元素。这些接口定义在 ISignatureUI 中,可通过 IViewerUI.getSignatureUI()
方法获取。主要包括以下几个接口:
ISignDocDialog
(opens new window): 用于显示签名文档对话框ISignVerifiedResultDialog
(opens new window): 用于显示签名验证结果对话框ISignedSignaturePropertiesDialog
(opens new window): 用于显示已签署的签名字段属性对话框
要自定义签名相关的 UI 元素,可以通过继承 SDK 内置的 Viewer UI 类(如 XViewerUI 或 TinyViewerUI),并重写 getSignatureUI
方法返回自定义的 ISignatureUI 实现。比如:
class CustomSignDocDialog implements ISignDocDialog {
private signatureField: ISignatureField;
private _isOpened = false;
async openWith(
signature: ISignatureField,
okCallback: (data: SignDocInfo, sign: DigestSignHandler) => Promise<void> | void,
cancelCallback?: () => Promise<void> | void
): Promise<void> {
this._isOpened = true;
this.signatureField = signature;
// 创建签名对话框界面
// 显示各项信息
// 注册 okCallback 和 cancelCallback 回调事件,当用户确认或者取消时会调用该回调
}
isOpened(): boolean {
return this._isOpened;
}
getCurrentSignature(): ISignatureField | undefined {
return this.signatureField;
}
hide(): void {
this._isOpened = false;
// 隐藏签名对话框
}
destroy(): void {
// 释放资源
}
}
class CustomSignatureUI implements ISignatureUI {
// 实现 ISignatureUI 接口中的方法
getSignDocumentDialog() {
return Promise.resolve(new CustomSignDocDialog())
}
...
}
class CustomViewerUI extends UIExtension.XViewerUI {
getSignatureUI() {
return Promise.resolve(new CustomSignatureUI());
}
}
// 使用自定义的 ViewerUI
new PDFUI({
viewerOptions: {
viewerUI: new CustomViewerUI()
}
})
在自定义的 ISignatureUI 实现中,可以根据需求定制各个对话框的外观和行为。例如,通过重写 ISignDocDialog.openWith
方法,可以在打开签名对话框时执行特定的自定义逻辑。