# EasySign
**Repository Path**: duxvfeng/EasySign
## Basic Information
- **Project Name**: EasySign
- **Description**: EasySignEasySignEasySignEasySignEasySignEasySign
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-05-03
- **Last Updated**: 2026-05-03
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# EasySign - 电子签章Spring Boot Starter
## 项目简介
EasySign是一个基于Apache PDFBox的电子签章Spring Boot Starter,提供了简单易用的API来对PDF文档进行数字签名和验证。
### 主要特性
- 支持多种签章类型:文本签名、图片签名、图文混合签名、不可见签名
- 基于标准的PKCS12证书格式
- 完整的签名验证功能
- 与Spring Boot无缝集成
- 适配JDK 17+
- 支持自定义签章位置、大小、颜色等
---
## 项目结构
```
EasySign/
├── pom.xml # 父POM
├── easy-sign-core/ # 核心功能模块
│ ├── pom.xml
│ └── src/main/java/com/easysign/core/
│ ├── exception/ # 异常类
│ │ └── SignException.java
│ ├── enums/ # 枚举类
│ │ └── SignatureType.java
│ ├── model/ # 数据模型
│ │ ├── SignaturePosition.java
│ │ ├── SignatureRequest.java
│ │ └── SignatureResult.java
│ ├── service/ # 核心服务
│ │ ├── PdfSigner.java
│ │ └── SignatureVerifier.java
│ └── util/ # 工具类
│ ├── CertificateManager.java
│ └── CertificateGenerator.java
├── easy-sign-autoconfigure/ # 自动配置模块
│ ├── pom.xml
│ └── src/main/
│ ├── java/com/easysign/autoconfigure/
│ │ ├── EasySignAutoConfiguration.java
│ │ ├── properties/
│ │ │ └── EasySignProperties.java
│ │ └── template/
│ │ └── EasySignTemplate.java
│ └── resources/META-INF/spring/
│ └── org.springframework.boot.autoconfigure.AutoConfiguration.imports
├── easy-sign-starter/ # Spring Boot Starter
│ └── pom.xml
└── easy-sign-demo/ # 使用示例
├── pom.xml
└── src/main/
├── java/com/easysign/demo/
│ ├── EasySignDemoApplication.java
│ ├── controller/
│ │ └── SignController.java
│ ├── service/
│ │ └── SignDemoService.java
│ └── runner/
│ └── DemoRunner.java
└── resources/
└── application.yml
```
---
## 快速开始
### 环境要求
- JDK 17+
- Maven 3.6+
- Spring Boot 3.2.x
### 步骤1: 编译项目
```bash
cd e:\code\EasySign
mvn clean install
```
### 步骤2: 生成测试证书
首次使用需要生成测试证书。项目提供了证书生成工具:
```java
// 运行CertificateGenerator的main方法
// 或者在启动demo项目时自动生成
```
或者使用keytool命令:
```bash
keytool -genkeypair -alias test-alias -keyalg RSA -keysize 2048 \
-storetype PKCS12 -keystore certs/test.p12 -storepass 123456 \
-dname "CN=Test User, OU=Test, O=Test Org, L=Beijing, ST=Beijing, C=CN"
```
### 步骤3: 添加依赖
在你的Spring Boot项目中添加依赖:
```xml
com.easysign
easy-sign-starter
1.0.0
```
### 步骤4: 配置参数
在`application.yml`中配置:
```yaml
easysign:
enabled: true
certificate:
key-store-path: certs/test.p12 # PKCS12证书路径
key-store-password: 123456 # 密钥库密码
key-alias: test-alias # 证书别名
key-password: 123456 # 密钥密码(可选)
default-signature:
signer-name: Test User # 默认签名者名称
reason: Document Approval # 默认签名原因
location: Beijing # 默认签名位置
visible: true # 默认是否可见
font-size: 14 # 默认字体大小
font-color: "#000000" # 默认字体颜色
```
### 步骤5: 使用示例
#### 5.1 基本文本签名
```java
@Autowired
private EasySignTemplate easySignTemplate;
public void signPdf() {
// 方式1: 使用便捷方法
SignatureResult result = easySignTemplate.signText(
"input.pdf", // 源PDF路径
"output_signed.pdf", // 输出PDF路径
"John Doe - CEO", // 签名文本
50, 50, 200, 60 // x, y, width, height
);
if (result.isSuccess()) {
System.out.println("签名成功: " + result.getOutputPath());
}
}
```
#### 5.2 指定页码的签名
```java
SignatureResult result = easySignTemplate.signText(
"input.pdf",
"output_signed.pdf",
"Approved by Legal",
2, // 页码(从1开始)
400, 50, 180, 50 // x, y, width, height
);
```
#### 5.3 图片签名
```java
SignatureResult result = easySignTemplate.signImage(
"input.pdf",
"output_signed.pdf",
"signature.png", // 签章图片路径
100, 100, 150, 80 // 位置和大小
);
```
#### 5.4 不可见签名(仅证书)
```java
SignatureResult result = easySignTemplate.signInvisible(
"input.pdf",
"output_signed.pdf"
);
```
#### 5.5 使用完整的请求对象
```java
SignatureRequest request = new SignatureRequest();
request.setSourcePdfPath("input.pdf");
request.setOutputPdfPath("output.pdf");
request.setType(SignatureType.TEXT_AND_IMAGE);
request.setTextContent("Approved");
request.setImagePath("stamp.png");
request.setPosition(new SignaturePosition(1, 50, 50, 200, 80));
request.setSignerName("John Doe");
request.setReason("Document Approval");
request.setLocation("Beijing");
request.setFontSize(16);
request.setFontColor("#FF0000");
SignatureResult result = easySignTemplate.sign(request);
```
#### 5.6 验证签名
```java
// 验证PDF中的所有签名
List results =
easySignTemplate.verify("signed.pdf");
for (SignatureVerifier.VerificationResult result : results) {
System.out.println("签名有效性: " + result.isValid());
System.out.println("签名者: " + result.getSignatureName());
System.out.println("签名时间: " + result.getSignTime());
System.out.println("证书主题: " + result.getSubjectDN());
}
// 快速检查是否有有效签名
boolean hasValid = easySignTemplate.hasValidSignature("signed.pdf");
```
#### 5.7 使用字节数组(适合Web上传场景)
```java
@PostMapping("/sign")
public byte[] signPdf(@RequestParam("file") MultipartFile file) throws IOException {
SignatureRequest request = new SignatureRequest();
request.setSourcePdfBytes(file.getBytes());
request.setType(SignatureType.TEXT);
request.setTextContent("Signed");
request.setPosition(new SignaturePosition(50, 50, 150, 60));
return easySignTemplate.signToBytes(request);
}
```
---
## API参考
### EasySignTemplate 主要方法
| 方法 | 描述 |
|------|------|
| `signText(source, output, text, x, y, w, h)` | 基本文本签名 |
| `signText(source, output, text, page, x, y, w, h)` | 指定页码的文本签名 |
| `signImage(source, output, imagePath, x, y, w, h)` | 图片签名 |
| `signInvisible(source, output)` | 不可见签名 |
| `sign(request)` | 使用完整请求对象签名 |
| `signToBytes(request)` | 签名并返回字节数组 |
| `verify(pdfPath)` | 验证PDF中的签名 |
| `verify(bytes)` | 从字节数组验证签名 |
| `hasValidSignature(pdfPath)` | 检查是否有有效签名 |
### SignatureType 签名类型
| 类型 | 描述 |
|------|------|
| `TEXT` | 纯文本签名 |
| `IMAGE` | 纯图片签名 |
| `TEXT_AND_IMAGE` | 图文混合签名 |
| `CERT_ONLY` | 仅证书(不可见)签名 |
### SignatureResult 签名结果
| 字段 | 描述 |
|------|------|
| `success` | 是否成功 |
| `message` | 结果消息 |
| `outputPath` | 输出文件路径 |
| `outputBytes` | 输出字节数组 |
| `signTime` | 签名时间 |
| `signerName` | 签名者名称 |
| `reason` | 签名原因 |
| `location` | 签名位置 |
### VerificationResult 验证结果
| 字段 | 描述 |
|------|------|
| `valid` | 签名是否有效 |
| `message` | 验证消息 |
| `signatureName` | 签名名称 |
| `reason` | 签名原因 |
| `location` | 签名位置 |
| `signTime` | 签名时间 |
| `subjectDN` | 证书主题 |
| `issuerDN` | 证书颁发者 |
| `certificate` | X509证书对象 |
---
## 验证案例
### 案例1: 基本文本签名
**场景**: 对合同PDF进行文本签名
```java
@Test
void testTextSignature() {
// 1. 准备测试PDF
String inputPdf = "contract.pdf";
String outputPdf = "contract_signed.pdf";
// 2. 执行签名
SignatureResult result = easySignTemplate.signText(
inputPdf, outputPdf,
"张三 - 总经理",
400, 100, 180, 60
);
// 3. 验证结果
assertTrue(result.isSuccess());
assertNotNull(result.getOutputPath());
// 4. 验证签名有效性
List verifyResults =
easySignTemplate.verify(outputPdf);
assertEquals(1, verifyResults.size());
assertTrue(verifyResults.get(0).isValid());
}
```
### 案例2: 多页文档签名
**场景**: 在多页文档的不同位置签名
```java
@Test
void testMultiPageSignature() {
String inputPdf = "multi_page.pdf";
String outputPdf = "multi_page_signed.pdf";
// 第1页底部签名
SignatureRequest request1 = new SignatureRequest();
request1.setSourcePdfPath(inputPdf);
request1.setType(SignatureType.TEXT);
request1.setTextContent("Page 1 Signature");
request1.setPosition(new SignaturePosition(1, 50, 50, 150, 50));
// 先签第1页...
// 第3页右下角签名
SignatureRequest request2 = new SignatureRequest();
request2.setSourcePdfPath(outputPdf);
request2.setType(SignatureType.TEXT);
request2.setTextContent("Approved");
request2.setPosition(new SignaturePosition(3, 450, 50, 100, 40));
// 再签第3页...
// 验证多个签名
List results =
easySignTemplate.verify(outputPdf);
assertEquals(2, results.size());
}
```
### 案例3: 图片签章(公章)
**场景**: 使用公章图片进行签名
```java
@Test
void testImageSignature() {
String inputPdf = "document.pdf";
String outputPdf = "document_stamped.pdf";
String stampImage = "company_stamp.png";
SignatureResult result = easySignTemplate.signImage(
inputPdf, outputPdf, stampImage,
300, 200, 120, 120 // 圆形公章通常120x120
);
assertTrue(result.isSuccess());
// 验证
List verifyResults =
easySignTemplate.verify(outputPdf);
assertTrue(verifyResults.get(0).isValid());
}
```
### 案例4: 不可见签名
**场景**: 只需要数字证书保护,不需要视觉标识
```java
@Test
void testInvisibleSignature() {
String inputPdf = "sensitive.pdf";
String outputPdf = "sensitive_signed.pdf";
// 不可见签名 - 只附加数字证书
SignatureResult result = easySignTemplate.signInvisible(
inputPdf, outputPdf
);
assertTrue(result.isSuccess());
// 验证 - 虽然视觉不可见,但数字签名有效
List verifyResults =
easySignTemplate.verify(outputPdf);
assertTrue(verifyResults.get(0).isValid());
assertEquals("Document Authentication", verifyResults.get(0).getReason());
}
```
### 案例5: 批量签名
**场景**: 对多个PDF文件进行批量签名
```java
@Test
void testBatchSigning() {
List pdfFiles = Arrays.asList(
"contract1.pdf", "contract2.pdf", "contract3.pdf"
);
List signedFiles = new ArrayList<>();
for (String pdf : pdfFiles) {
String output = pdf.replace(".pdf", "_signed.pdf");
SignatureResult result = easySignTemplate.signText(
pdf, output, "批量签名", 50, 50, 150, 50
);
if (result.isSuccess()) {
signedFiles.add(output);
}
}
// 验证所有签名
for (String signed : signedFiles) {
assertTrue(easySignTemplate.hasValidSignature(signed));
}
}
```
---
## REST API示例
### 测试服务状态
```bash
GET http://localhost:8080/api/sign/test
响应:
{
"status": "ok",
"message": "EasySign服务运行正常"
}
```
### 文本签名
```bash
POST http://localhost:8080/api/sign/text
Content-Type: multipart/form-data
参数:
- file: PDF文件
- text: 签名文本(可选, 默认"Digital Signature")
- x: X坐标(可选, 默认50)
- y: Y坐标(可选, 默认50)
- width: 宽度(可选, 默认200)
- height: 高度(可选, 默认80)
示例:
curl -X POST \
-F "file=@contract.pdf" \
-F "text=Approved by CEO" \
-F "x=400" \
-F "y=50" \
http://localhost:8080/api/sign/text \
-o contract_signed.pdf
```
### 图片签名
```bash
POST http://localhost:8080/api/sign/image
Content-Type: multipart/form-data
参数:
- file: PDF文件
- imageFile: 签章图片
- x, y, width, height: 位置和大小
示例:
curl -X POST \
-F "file=@document.pdf" \
-F "imageFile=@stamp.png" \
http://localhost:8080/api/sign/image \
-o document_signed.pdf
```
### 不可见签名
```bash
POST http://localhost:8080/api/sign/invisible
Content-Type: multipart/form-data
参数:
- file: PDF文件
示例:
curl -X POST \
-F "file=@sensitive.pdf" \
http://localhost:8080/api/sign/invisible \
-o sensitive_signed.pdf
```
### 验证签名
```bash
POST http://localhost:8080/api/sign/verify
Content-Type: multipart/form-data
参数:
- file: 已签名的PDF文件
示例:
curl -X POST \
-F "file=@signed.pdf" \
http://localhost:8080/api/sign/verify
响应示例:
{
"totalSignatures": 2,
"hasValidSignature": true,
"signatures": [
{
"valid": true,
"message": "签名验证通过",
"signatureName": "Test User",
"reason": "Document Approval",
"location": "Beijing",
"filter": "Adobe.PPKLite",
"subFilter": "adbe.pkcs7.detached",
"signTime": "2026-05-03T10:30:00",
"subjectDN": "CN=Test User, O=Test Org",
"issuerDN": "CN=Test User, O=Test Org"
}
]
}
```
---
## 配置详解
### 证书配置
| 配置项 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| `easysign.certificate.key-store-path` | String | 是 | PKCS12证书文件路径 |
| `easysign.certificate.key-store-password` | String | 是 | 密钥库访问密码 |
| `easysign.certificate.key-alias` | String | 否 | 证书别名,不填则使用第一个别名 |
| `easysign.certificate.key-password` | String | 否 | 密钥密码,不填则使用密钥库密码 |
### 默认签名配置
| 配置项 | 类型 | 默认值 | 说明 |
|--------|------|--------|------|
| `easysign.default-signature.signer-name` | String | null | 默认签名者名称 |
| `easysign.default-signature.reason` | String | null | 默认签名原因 |
| `easysign.default-signature.location` | String | null | 默认签名位置 |
| `easysign.default-signature.visible` | boolean | true | 是否可见 |
| `easysign.default-signature.font-size` | float | 12 | 字体大小 |
| `easysign.default-signature.font-color` | String | "#000000" | 字体颜色(十六进制) |
| `easysign.default-signature.image-path` | String | null | 默认签章图片路径 |
| `easysign.default-signature.timestamp-url` | String | null | 时间戳服务URL |
---
## 技术说明
### 数字签名原理
EasySign使用标准的PDF数字签名机制:
1. **证书管理**: 使用PKCS12格式的证书库,包含私钥和证书链
2. **签名算法**: SHA-256 with RSA
3. **PDF签名格式**: Adobe.PPKLite / adbe.pkcs7.detached
4. **验证机制**: 使用Bouncy Castle库验证CMS签名数据
### 坐标系统
PDF使用左下角为原点的坐标系统:
```
Y轴
^
|
| (x + width, y + height)
| +------------------+
| | |
| | 签名区域 |
| | |
| +------------------+
| (x, y)
|
+----------------------------------> X轴
(0, 0)
```
### 依赖库
| 库 | 版本 | 用途 |
|---|------|------|
| Apache PDFBox | 2.0.29 | PDF文档处理 |
| Bouncy Castle | 1.77 | 密码学操作 |
| Spring Boot | 3.2.5 | 自动配置和依赖注入 |
---
## 常见问题
### Q1: 证书生成后无法使用
**解决方案**:
- 确保证书格式为PKCS12(.p12或.pfx)
- 检查密码是否正确
- 使用keytool查看证书信息:
```bash
keytool -list -v -storetype PKCS12 -keystore test.p12
```
### Q2: 中文显示乱码
**原因**: PDFBox内置字体不支持中文
**解决方案**:
- 使用图片签章替代文本签章
- 或扩展PdfSigner类,嵌入中文字体
### Q3: 签名后PDF无法打开
**可能原因**:
- 原始PDF已加密
- PDF格式有问题
**解决方案**:
- 先解密PDF再签名
- 使用标准PDF工具检查PDF有效性
### Q4: 签名验证失败
**可能原因**:
- PDF在签名后被修改
- 证书已过期或被撤销
- 时间戳服务不可用
**排查步骤**:
1. 检查PDF是否被篡改
2. 验证证书有效性
3. 检查签名时间
### Q5: 如何在生产环境使用正式证书
**步骤**:
1. 向CA机构申请正式的数字证书
2. 将证书导出为PKCS12格式
3. 配置证书路径和密码
4. 考虑配置时间戳服务(TSA)
---
## 扩展开发
### 自定义签名外观
继承或修改`PdfSigner`类的`createVisualSignature`方法来自定义签名外观。
### 添加时间戳支持
配置`timestamp-url`参数,使用RFC 3161时间戳服务:
```yaml
easysign:
default-signature:
timestamp-url: http://timestamp.digicert.com
```
### 多证书支持
创建多个`CertificateManager`实例,使用不同的证书进行签名:
```java
CertificateManager cert1 = new CertificateManager("cert1.p12", "pass1", "alias1", "pass1");
CertificateManager cert2 = new CertificateManager("cert2.p12", "pass2", "alias2", "pass2");
PdfSigner signer1 = new PdfSigner(cert1);
PdfSigner signer2 = new PdfSigner(cert2);
```
---
## 版本历史
### v1.0.0 (2026-05-03)
- 初始版本发布
- 支持文本签名、图片签名、不可见签名
- 完整的签名验证功能
- Spring Boot Starter自动配置
- JDK 17+支持
---
## 许可证
本项目遵循MIT许可证。
---
## 联系方式
如有问题或建议,请提交Issue。