You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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端实现

  1. 安装依赖:
npm install pdfjs-dist
  1. 读取签名信息的示例代码:
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文件,无需后端中转:

  1. 安装依赖:
npm install pdfjs-dist
  1. 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签名数据:

  1. 安装依赖:
npm install pdfjs-dist node-forge
  1. 扩展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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 12:10:59