PHP项目电子签章集成方案
技术选型与架构设计
1 常见电子签章平台对比
| 平台 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| e签宝 | 接口完善,PHP SDK支持好 | 费用较高 | 企业级应用 |
| 法大大 | 合规性好,本地签章支持 | 接入门槛较高 | 合同类业务 |
| 契约锁 | 加密技术强,自定义程度高 | 文档较少 | 金融、政务 |
| 自建方案 | 完全可控,无额外费用 | 开发成本高 | 内部管理系统 |
| OpenSSL + TCPDF | 免费开源 | 法律效力需评估 | 非强制场合 |
2 架构建议
前端(Vue/React) → 后端PHP(Laravel/ThinkPHP) → 电子签章服务(第三方API/自建)
↓
签名服务器/RSA密钥存储
主流第三方平台集成方案
1 e签宝集成(推荐)
步骤1: 安装SDK

composer require esign/esign-php-sdk
步骤2: 配置参数
// config/esign.php
return [
'app_id' => 'your_app_id',
'secret' => 'your_app_secret',
'api_url' => 'https://open.esign.cn', // 正式环境
'notify_url' => 'https://yourdomain.com/api/esign/notify'
];
步骤3: 创建签署流程
use Esign\EsignClient;
class SignService
{
private $client;
public function __construct()
{
$this->client = new EsignClient(
config('esign.app_id'),
config('esign.secret'),
config('esign.api_url')
);
}
/**
* 创建合同并发起签署
*/
public function createContract(array $signers, string $filePath)
{
// 1. 上传文件
$fileId = $this->uploadFile($filePath);
// 2. 创建签署流程
$flowId = $this->createSignFlow($fileId, $signers);
// 3. 设置签署位置
$this->setSignPositions($flowId, $signers);
// 4. 发起签署
return $this->startSign($flowId);
}
private function uploadFile(string $path): string
{
$response = $this->client->uploadFile([
'file' => new \CURLFile(realpath($path))
]);
return $response['data']['fileId'];
}
private function createSignFlow(string $fileId, array $signers): string
{
$data = [
'name' => '合同签署-' . date('YmdHis'),
'docs' => [['fileId' => $fileId]],
'signers' => [],
'noticeConfig' => [
'noticeTypes' => ['SMS', 'EMAIL']
]
];
foreach ($signers as $index => $signer) {
$data['signers'][] = [
'signerAccount' => [
'thirdPartyUserId' => $signer['user_id'],
'name' => $signer['name'],
'idType' => 'CRED_PSN_CH_IDCARD',
'idNumber' => $signer['id_card']
],
'signOrder' => $index + 1
];
}
$response = $this->client->createSignFlow($data);
return $response['data']['flowId'];
}
private function setSignPositions(string $flowId, array $signers)
{
foreach ($signers as $index => $signer) {
$this->client->addSignFieldPosition($flowId, [
'signerAccountId' => $signer['user_id'],
'signfields' => [
[
'type' => 0, // 0-手写签名,1-印章
'posPage' => 1,
'posX' => $signer['pos_x'] ?? 200,
'posY' => $signer['pos_y'] ?? 300
]
]
]);
}
}
private function startSign(string $flowId)
{
return $this->client->startSignFlow($flowId);
}
/**
* 处理回调通知
*/
public function handleNotify(array $params)
{
// 验签
if (!$this->verifyNotify($params)) {
throw new \Exception('Invalid signature');
}
$flowId = $params['flowId'];
$status = $params['signStatus']; // 2-签署完成
if ($status == 2) {
// 下载已签署文件
$pdfContent = $this->downloadSignedFile($flowId);
// 保存到本地
Storage::put("contracts/{$flowId}.pdf", $pdfContent);
}
return response()->json(['code' => 0]);
}
private function downloadSignedFile(string $flowId): string
{
$response = $this->client->downloadSignedFile($flowId);
return $response['data']['content']; // base64格式
}
private function verifyNotify(array $params): bool
{
$signature = $params['signature'] ?? '';
$data = $params['data'] ?? '';
return $this->client->verifySignature($data, $signature);
}
}
2 法大大集成
// 使用法大大PHP SDK
use Fadada\SignClient;
$client = new SignClient([
'app_id' => 'your_app_id',
'secret' => 'your_secret',
'version' => 'v2'
]);
// 1. 实名认证
$authResult = $client->personalAuth([
'name' => '张三',
'id_card' => '110101199001011234',
'mobile' => '13800138000'
]);
// 2. 创建合同
$contractId = $client->createContract([ => '合作协议',
'template_id' => 'template_001',
'params' => [
'party_a' => '公司A',
'party_b' => '公司B',
'amount' => 10000
]
]);
// 3. 获取签署链接
$signUrl = $client->getSignUrl([
'contract_id' => $contractId,
'user_id' => $authResult['user_id'],
'redirect_url' => 'https://yourdomain.com/sign/success'
]);
// 返回给前端跳转
return redirect($signUrl);
自建电子签章方案
1 基于OpenSSL + TCPDF
技术栈:
- PHP 7.4+
- OpenSSL扩展
- TCPDF库
- 签章图片(公章、手写签名)
步骤1: 生成CA证书链
# 生成根证书 openssl req -x509 -newkey rsa:2048 -keyout rootCA.key -out rootCA.crt -days 3650 # 生成员工证书 openssl req -new -newkey rsa:2048 -keyout user.key -out user.csr openssl x509 -req -in user.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out user.crt -days 365
步骤2: PHP签名实现
class CustomSignService
{
private $certPath;
private $keyPath;
private $signImagePath;
public function __construct()
{
$this->certPath = storage_path('certs/user.crt');
$this->keyPath = storage_path('certs/user.key');
$this->signImagePath = storage_path('signatures/seal.png');
}
/**
* 给PDF添加数字签名
*/
public function signPdf(string $inputPdf, string $outputPdf, array $position = [100, 200])
{
// 1. 加载PDF
$pdf = new TCPDF();
$pdf->setSourceFile($inputPdf);
// 2. 添加签章图片
$pdf->Image(
$this->signImagePath,
$position[0],
$position[1],
50, // 宽度
50, // 高度
'PNG'
);
// 3. 计算文档哈希
$content = file_get_contents($inputPdf);
$hash = hash('sha256', $content);
// 4. 生成数字签名
$signature = $this->generateSignature($hash);
// 5. 嵌入签名信息到PDF元数据
$pdf->setSignature(
$this->certPath,
$this->keyPath,
$signature,
'Signing by: User1, Date: ' . date('Y-m-d H:i:s'),
2, // 签名可见度
[] // 额外选项
);
// 6. 输出PDF
$pdf->Output($outputPdf, 'F');
return $outputPdf;
}
private function generateSignature(string $hash): string
{
$privateKey = openssl_pkey_get_private(file_get_contents($this->keyPath));
openssl_sign($hash, $signature, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($signature);
}
/**
* 验证签名
*/
public function verifySignature(string $pdfPath): bool
{
$pdf = new TCPDF();
$pdfData = $pdf->parsePdf($pdfPath);
$storedSignature = $pdfData['signature'] ?? '';
$storedHash = $pdfData['hash'] ?? '';
// 重新计算文档哈希
$content = file_get_contents($pdfPath);
$computedHash = hash('sha256', $content);
// 验证签名
$publicKey = openssl_pkey_get_public(file_get_contents($this->certPath));
$verified = openssl_verify(
$storedHash,
base64_decode($storedSignature),
$publicKey,
OPENSSL_ALGO_SHA256
);
return $verified && ($storedHash === $computedHash);
}
}
2 手写签名采集方案
前端实现(HTML5 Canvas):
<canvas id="signaturePad" width="400" height="200"></canvas>
<button onclick="saveSignature()">保存签名</button>
<script src="https://cdn.jsdelivr.net/npm/signature_pad@4.0.0/dist/signature_pad.umd.min.js"></script>
<script>
const canvas = document.getElementById('signaturePad');
const signaturePad = new SignaturePad(canvas);
function saveSignature() {
if (signaturePad.isEmpty()) {
alert('请先签名');
return;
}
const dataURL = signaturePad.toDataURL('image/png');
// 发送到后端
fetch('/api/save-signature', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content
},
body: JSON.stringify({
signature: dataURL,
user_id: 'current_user_id'
})
}).then(response => response.json())
.then(data => {
alert('签名保存成功');
});
}
</script>
后端保存:
public function saveSignature(Request $request)
{
$signatureData = $request->input('signature');
// 去除base64前缀 data:image/png;base64,
$signatureData = preg_replace('/^data:image\/\w+;base64,/', '', $signatureData);
$signatureData = base64_decode($signatureData);
$filename = 'signatures/' . $request->user()->id . '_' . time() . '.png';
Storage::put($filename, $signatureData);
// 保存到数据库
UserSignature::updateOrCreate(
['user_id' => $request->user()->id],
['signature_path' => $filename]
);
return response()->json(['success' => true]);
}
数据库表设计
-- 签章记录表 CREATE TABLE `sign_records` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `contract_id` varchar(64) NOT NULL COMMENT '合同编号', `signer_id` bigint unsigned NOT NULL COMMENT '签署人', `sign_type` tinyint NOT NULL DEFAULT '1' COMMENT '1-平台签 2-自建签', `sign_status` tinyint NOT NULL DEFAULT '0' COMMENT '0-待签 1-已签 2-拒签', `signed_at` timestamp NULL DEFAULT NULL COMMENT '签署时间', `signature_path` varchar(255) DEFAULT NULL COMMENT '签章文件路径', `ip_address` varchar(45) DEFAULT NULL COMMENT '签署IP', `device_info` varchar(255) DEFAULT NULL COMMENT '设备信息', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `idx_contract_id` (`contract_id`), KEY `idx_signer_id` (`signer_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 签章配置表 CREATE TABLE `sign_configs` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `user_id` bigint unsigned NOT NULL, `provider` varchar(32) NOT NULL DEFAULT 'esign' COMMENT '平台类型', `account_id` varchar(128) DEFAULT NULL COMMENT '平台账户ID', `seal_data` text COMMENT '印章数据(生物特征)', `cert_serial` varchar(128) DEFAULT NULL COMMENT '证书序列号', `is_active` tinyint NOT NULL DEFAULT '1', `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_provider` (`user_id`, `provider`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
安全最佳实践
1 密钥管理
// 使用Laravel的加密服务 use Illuminate\Support\Facades\Crypt; // 存储私钥前加密 $encryptedKey = Crypt::encryptString($privateKeyContent); // 使用时解密 $privateKey = Crypt::decryptString($encryptedKey);
2 防篡改机制
class SignVerifyMiddleware
{
public function handle($request, \Closure $next)
{
// 验证请求完整性
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Timestamp');
$nonce = $request->header('X-Nonce');
$params = $request->all();
ksort($params);
$queryString = http_build_query($params);
$toSign = "{$timestamp}{$nonce}{$queryString}";
$expectedSignature = hash_hmac('sha256', $toSign, config('app.api_secret'));
if (!hash_equals($expectedSignature, $signature)) {
return response()->json(['error' => 'Invalid signature'], 403);
}
return $next($request);
}
}
3 日志审计
// 记录所有签名操作
\Log::channel('signature')->info('Sign contract', [
'user_id' => auth()->id(),
'contract_id' => $contractId,
'ip' => request()->ip(),
'user_agent' => request()->userAgent(),
'action' => 'create_sign_flow',
'result' => $result
]);
性能优化建议
- PDF预处理:使用
Ghostscript压缩PDF文件 - 异步处理:签名完成后通过队列任务下载文件
- 缓存方案:缓存公钥信息和证书链
- CDN加速:静态签章图片使用CDN
- 负载均衡:签名服务独立部署
合规性注意事项
| 要求 | 实现方式 |
|---|---|
| 实名认证 | 对接公安/银联实名接口 |
| 意愿认证 | 短信验证码+人脸识别 |
| 时间戳服务 | 对接国家授时中心 |
| 证据链保存 | 记录完整操作日志+IP+设备指纹 |
| 加密存储 | 敏感字段AES-256加密 |
测试环境搭建
# 使用Docker搭建测试环境 docker run -d --name esign-test \ -p 8080:80 \ -e ESIGN_MODE=test \ esign/esign-server:latest # PHP单元测试示例 php artisan test tests/Unit/SignServiceTest
选择建议:
- 初创公司:优先使用e签宝/法大大等成熟平台
- 中型企业:可以采用混合方案(核心业务用第三方,次要业务自建)
- 大型企业:自建方案 + 脱敏测试环境
如果需要具体平台的SDK示例或开发过程中遇到问题,可以提供更多细节。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播