如何在Node.js/React中提取PDF签名信息?安装依赖遇阻
解决方案:读取已签名PDF的签名信息(Node.js + React)
因为@ninja-labs/verify-pdf安装报错,且你仅需读取签名信息无需验证,以下是几个更稳定的替代方案:
方案1:使用pdfjs-dist(支持Node.js和React)
这个库是Mozilla官方的PDF解析工具,既能在Node.js后端读取本地PDF,也能在React前端处理用户上传的文件,兼容性强。
Node.js端实现
- 安装依赖:
npm install pdfjs-dist
- 读取签名信息的示例代码:
const pdfjsLib = require('pdfjs-dist'); const fs = require('fs'); // 配置Node.js环境的Worker路径 pdfjsLib.GlobalWorkerOptions.workerSrc = require.resolve('pdfjs-dist/build/pdf.worker.entry'); async function extractPdfSignatures(pdfFilePath) { const pdfBuffer = fs.readFileSync(pdfFilePath); const pdfDoc = await pdfjsLib.getDocument({ data: new Uint8Array(pdfBuffer) }).promise; const signatureList = []; // 优先从文档级AcroForm字段中查找签名(部分PDF签名存在这里) if (pdfDoc.catalog.AcroForm?.Fields) { for (const field of pdfDoc.catalog.AcroForm.Fields) { if (field.fieldType === 'Sig') { const sigDict = field.V; const sigInfo = { signer: sigDict?.Name || field.fieldName || '未知签名人', signatureType: sigDict?.SubFilter || '数字签名', signTime: sigDict?.M ? new Date(sigDict.M).toLocaleString() : '未记录时间' }; signatureList.push(sigInfo); } } } // 遍历页面注解补充签名信息(部分PDF签名在页面层级) for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) { const page = await pdfDoc.getPage(pageNum); const annotations = await page.getAnnotations(); for (const annot of annotations) { if (annot.subtype === 'Widget' && annot.fieldType === 'Sig') { const existingSig = signatureList.find(s => s.signer === annot.fieldName); if (!existingSig) { const sigDict = annot.fieldObject?.V; const sigInfo = { signer: sigDict?.Name || annot.fieldName || '未知签名人', signatureType: sigDict?.SubFilter || '数字签名', signTime: sigDict?.M ? new Date(sigDict.M).toLocaleString() : '未记录时间' }; signatureList.push(sigInfo); } } } } return signatureList; } // 使用示例 extractPdfSignatures('./your-signed-document.pdf') .then(signatures => console.log('提取的签名信息:', signatures)) .catch(err => console.error('解析失败:', err));
React前端实现
直接在浏览器端处理用户上传的PDF文件,无需后端中转:
- 安装依赖:
npm install pdfjs-dist
- React组件示例:
import { useState } from 'react'; import * as pdfjsLib from 'pdfjs-dist'; // 加载Worker(浏览器端自动处理路径) import 'pdfjs-dist/build/pdf.worker.entry'; const PdfSignatureViewer = () => { const [signatures, setSignatures] = useState([]); const [loading, setLoading] = useState(false); const handleFileUpload = async (e) => { const file = e.target.files[0]; if (!file) return; setLoading(true); try { const arrayBuffer = await file.arrayBuffer(); const pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; const sigList = []; // 从AcroForm字段提取签名 if (pdfDoc.catalog.AcroForm?.Fields) { for (const field of pdfDoc.catalog.AcroForm.Fields) { if (field.fieldType === 'Sig') { const sigDict = field.V; sigList.push({ signer: sigDict?.Name || field.fieldName || '未知签名人', signatureType: sigDict?.SubFilter || '数字签名', signTime: sigDict?.M ? new Date(sigDict.M).toLocaleString() : '未记录时间' }); } } } // 遍历页面补充签名信息 for (let pageNum = 1; pageNum <= pdfDoc.numPages; pageNum++) { const page = await pdfDoc.getPage(pageNum); const annotations = await page.getAnnotations(); for (const annot of annotations) { if (annot.subtype === 'Widget' && annot.fieldType === 'Sig') { const isDuplicate = sigList.some(s => s.signer === annot.fieldName); if (!isDuplicate) { const sigDict = annot.fieldObject?.V; sigList.push({ signer: sigDict?.Name || annot.fieldName || '未知签名人', signatureType: sigDict?.SubFilter || '数字签名', signTime: sigDict?.M ? new Date(sigDict.M).toLocaleString() : '未记录时间' }); } } } } setSignatures(sigList); } catch (err) { alert('解析PDF失败,请确保文件是已签名的PDF格式'); console.error(err); } finally { setLoading(false); } }; return ( <div className="pdf-signature-reader"> <input type="file" accept=".pdf" onChange={handleFileUpload} disabled={loading} /> {loading && <p>正在解析PDF...</p>} {signatures.length > 0 && ( <div className="signature-list"> <h3>签名信息</h3> <ul> {signatures.map((sig, idx) => ( <li key={idx} className="signature-item"> <p><strong>签名人:</strong> {sig.signer}</p> <p><strong>签名时间:</strong> {sig.signTime}</p> <p><strong>签名类型:</strong> {sig.signatureType}</p> </li> ))} </ul> </div> )} </div> ); }; export default PdfSignatureViewer;
方案2:结合pdfjs-dist和node-forge提取证书级细节
如果需要读取签名人的证书详情(比如机构名称、证书有效期),可以用node-forge解析PKCS#7签名数据:
- 安装依赖:
npm install pdfjs-dist node-forge
- 扩展Node.js端的解析逻辑:
const forge = require('node-forge'); // 在之前的extractPdfSignatures函数中,当获取到sigDict时添加: if (sigDict?.Contents) { try { // 解码Base64格式的签名内容 const pkcs7Pem = atob(sigDict.Contents); const pkcs7 = forge.pkcs7.messageFromPem(pkcs7Pem); // 提取签名人证书 const cert = pkcs7.certificates[0]; if (cert) { sigInfo.signerCert = { commonName: cert.subject.getField('CN').value, organization: cert.subject.getField('O')?.value || '无', validFrom: cert.validity.notBefore.toLocaleString(), validTo: cert.validity.notAfter.toLocaleString() }; } } catch (err) { console.warn('解析证书信息失败:', err); } }
注意事项
- 不同PDF生成工具的签名字段存储结构可能有差异,部分老旧PDF的签名信息可能需要调整解析逻辑
- 前端处理大PDF时,建议添加加载状态和错误提示,避免页面卡顿
- 如果后端需要批量处理PDF,可将解析逻辑封装为API接口,前端调用接口获取签名信息
内容的提问来源于stack exchange,提问作者Ali Hamedani
相关产品推荐
相关产品推荐

