esign-oauth2-handwritten-sign.md 17 KB

e签宝OAuth2.0手写签署流程说明

概述

本文档说明如何使用e签宝官方SDK(paas-sdk-3.1.4.jar)实现OAuth2.0鉴权方式的手写签署功能。

OAuth2.0鉴权机制

1. 认证流程

e签宝使用OAuth2.0客户端凭证模式(client_credentials)进行API鉴权:

┌──────────┐                  ┌──────────┐                  ┌──────────┐
│  应用    │                  │ e签宝API │                  │ e签宝    │
│          │                  │  网关    │                  │ 认证服务  │
└────┬─────┘                  └────┬─────┘                  └────┬─────┘
     │                              │                              │
     │ 1. 请求access_token          │                              │
     │ (AppId + AppSecret)          │                              │
     ├─────────────────────────────>│                              │
     │                              ├─────────────────────────────>│
     │                              │                              │
     │                              │ 2. 返回access_token          │
     │                              │<─────────────────────────────┤
     │ 3. 返回access_token          │                              │
     │<─────────────────────────────┤                              │
     │                              │                              │
     │ 4. 使用access_token调用API   │                              │
     ├─────────────────────────────>│                              │
     │                              │                              │
     │ 5. 返回API响应               │                              │
     │<─────────────────────────────┤                              │
     │                              │                              │

2. 获取Access Token

请求方式: GET
请求URL: {apiUrl}/v1/oauth2/access_token

请求参数:

  • appId: 应用ID
  • secret: 应用密钥
  • grantType: 固定值 client_credentials

请求头:

X-Tsign-Open-App-Id: {appId}
X-Tsign-Open-App-Secret: {secret}
Content-Type: application/json

响应示例:

{
    "code": 0,
    "message": "成功",
    "data": {
        "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "expiresIn": 7200,
        "refreshToken": "refresh_token_string"
    }
}

Token有效期: 通常为7200秒(2小时),需要定期刷新

3. 使用Access Token调用API

所有API请求需要在请求头中携带access_token:

请求头:

X-Tsign-Open-App-Id: {appId}
X-Tsign-Open-Token: {access_token}
Content-Type: application/json; charset=UTF-8

手写签署流程

完整业务流程

┌─────────────────────────────────────────────────────────────┐
│  步骤1: OAuth2.0获取访问令牌                                │
│  POST /v1/oauth2/access_token                              │
│  输入: AppId + AppSecret                                    │
│  输出: access_token                                         │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤2: 创建或获取签署人账号                                │
│  POST /v3/accounts/createByThirdPartyUserId                │
│  输入: mobile + name + idNumber                             │
│  输出: accountId                                            │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤3: 上传合同文件(如需)                                │
│  POST /v3/files/getUploadUrl                               │
│  输入: fileName + fileSize                                  │
│  输出: uploadUrl + fileId                                   │
│  然后: PUT uploadUrl (上传文件)                             │
│  最后: POST /v3/files/{fileId}/uploadComplete              │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤4: 创建签署流程                                        │
│  POST /v3/signflows                                        │
│  输入: docs + signers (包含手写签署区域)                    │
│  输出: flowId                                               │
│                                                             │
│  关键配置:                                                 │
│  - signFieldStyle: 1 (手写签名)                             │
│  - signFieldType: 5 (签名类型)                              │
│  - autoExecute: false (手动签署)                            │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤5: 启动签署流程                                        │
│  POST /v3/signflows/{flowId}/start                         │
│  输入: flowId                                               │
│  输出: 成功/失败                                            │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤6: 获取手写签署链接                                    │
│  GET /v3/signflows/{flowId}/signers/{accountId}/signUrl    │
│  输入: flowId + accountId                                   │
│  输出: signUrl (签署人通过此链接进行手写签名)               │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤7: 签署人进行手写签署                                  │
│  打开signUrl,在签署页面进行手写签名                        │
│  - 可以在手机或PC端进行手写                                 │
│  - 支持触摸屏手写和鼠标手写                                 │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤8: 查询签署状态                                        │
│  GET /v3/signflows/{flowId}                                │
│  输入: flowId                                               │
│  输出: flowStatus (0-草稿, 1-签署中, 2-签署完成, ...)      │
└─────────────────────────────────────────────────────────────┘
                            ↓
┌─────────────────────────────────────────────────────────────┐
│  步骤9: 下载已签署文档                                      │
│  GET /v3/signflows/{flowId}/documents                      │
│  输入: flowId                                               │
│  输出: 已签署的PDF文档下载地址                              │
└─────────────────────────────────────────────────────────────┘

代码实现

1. OAuth2.0工具类 (EsignSdkUtil)

package com.loan.system.utils;

import cn.hutool.http.HttpRequest;
import cn.hutool.json.JSONObject;
import lombok.extern.slf4j.Slf4j;

/**
 * e签宝OAuth2.0鉴权工具类
 * 使用OAuth2.0客户端凭证模式(client_credentials)获取访问令牌
 */
@Slf4j
public class EsignSdkUtil {
    
    private static volatile String cachedToken;
    private static volatile long tokenExpireTime;
    
    /**
     * 获取OAuth2.0访问令牌
     */
    public static String getAccessToken(String appId, String appSecret, String apiUrl) {
        // 检查缓存的token是否仍然有效
        if (cachedToken != null && System.currentTimeMillis() < (tokenExpireTime - 5 * 60 * 1000)) {
            return cachedToken;
        }
        
        String url = apiUrl + "/v1/oauth2/access_token";
        String getUrl = url + "?appId=" + appId + "&secret=" + appSecret + "&grantType=client_credentials";
        
        HttpResponse response = HttpRequest.get(getUrl)
                .header("X-Tsign-Open-App-Id", appId)
                .header("X-Tsign-Open-App-Secret", appSecret)
                .execute();
        
        JSONObject result = JSONUtil.parseObj(response.body());
        if (result.getInt("code") == 0) {
            JSONObject data = result.getJSONObject("data");
            String token = data.getStr("token");
            Integer expiresIn = data.getInt("expiresIn", 7200);
            
            cachedToken = token;
            tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000L;
            
            return token;
        }
        
        return null;
    }
}

2. 创建手写签署流程

EsignServiceImpl.createSignFlow() 方法中,关键配置如下:

// 创建手写签署区域
private List<Map<String, Object>> createSignFields(int signOrder, String signerName) {
    List<Map<String, Object>> fields = new ArrayList<>();
    Map<String, Object> field = new HashMap<>();
    field.put("fileId", 1); // 文档序号
    field.put("signerName", signerName);
    field.put("autoExecute", false); // 不自动执行,需要手动签署
    field.put("signFieldType", 5); // 5-签名类型
    field.put("signFieldStyle", 1); // 1-手写签名(重要!)
    field.put("signFieldStyleType", 1); // 1-手写签名样式
    field.put("page", 1); // 页码
    field.put("x", 100); // X坐标
    field.put("y", 200); // Y坐标
    field.put("width", 200); // 宽度
    field.put("height", 50); // 高度
    fields.add(field);
    return fields;
}

3. 获取手写签署链接

@Override
public String getCustomerSignUrl(String flowId, String accountId, String mobile, String name) {
    // 使用OAuth2.0鉴权获取签署链接
    String url = esignProperties.getApiUrl() + "/v3/signflows/" + flowId 
            + "/signers/" + accountId + "/signUrl";
    
    JSONObject params = new JSONObject();
    params.put("accountId", accountId);
    params.put("urlType", 1); // 1-移动端H5,2-PC端
    
    // sendRequest方法内部使用OAuth2.0 token
    JSONObject result = sendRequest(url, params);
    if (result != null && result.getInt("code") == 0) {
        return result.getJSONObject("data").getStr("signUrl");
    }
    return null;
}

关键配置说明

手写签署关键参数

参数 说明
signFieldStyle 1 手写签名样式(必须为1才能手写签署)
signFieldType 5 签名类型
signFieldStyleType 1 手写签名样式类型
autoExecute false 不自动执行,需要签署人手动签署
urlType 12 签署链接类型:1-移动端H5,2-PC端

OAuth2.0 Token管理

  1. Token缓存: 实现token缓存机制,避免频繁请求
  2. 自动刷新: 在token过期前5分钟自动刷新
  3. 错误处理: token失效时自动重新获取

API接口说明

1. 创建签署流程

接口: POST /v3/signflows

请求体关键字段:

{
    "autoExecute": false,
    "businessScene": "合同签署",
    "docs": [
        {
            "fileId": "文件ID",
            "fileName": "合同.pdf"
        }
    ],
    "signers": [
        {
            "signerAccount": {
                "signerAccountId": "签署人账号ID",
                "mobile": "手机号",
                "name": "姓名"
            },
            "signOrder": 1,
            "signFields": [
                {
                    "fileId": 1,
                    "signFieldStyle": 1,
                    "signFieldType": 5,
                    "signFieldStyleType": 1,
                    "page": 1,
                    "x": 100,
                    "y": 200,
                    "width": 200,
                    "height": 50
                }
            ]
        }
    ]
}

2. 获取签署链接

接口: GET /v3/signflows/{flowId}/signers/{accountId}/signUrl

请求参数:

  • accountId: 签署人账号ID
  • urlType: 链接类型(1-移动端,2-PC端)

响应:

{
    "code": 0,
    "data": {
        "signUrl": "https://..."
    }
}

3. 启动签署流程

接口: POST /v3/signflows/{flowId}/start

启动后,签署人才能通过签署链接进行签署。

使用SDK的说明

当前实现方式

当前代码使用HTTP方式直接调用e签宝API,但使用了OAuth2.0标准鉴权方式:

  • 使用 EsignSdkUtil 工具类获取OAuth2.0 token
  • 所有API请求都携带OAuth2.0 token
  • 符合OAuth2.0标准规范

使用官方SDK(可选)

如果希望完全使用官方SDK(paas-sdk-3.1.4.jar),可以参考以下方式:

  1. 初始化SDK客户端:

    // 根据SDK文档初始化ServiceClient
    ServiceClient client = EsignsdkServiceFactory.buildServiceClient(oauth2Config);
    
  2. 调用SDK方法:

    // 使用SDK的方法替代HTTP请求
    client.createSignFlow(params);
    client.getSignUrl(flowId, accountId);
    

注意: SDK的具体API请参考e签宝官方文档或联系技术支持。

配置说明

application-dev.yaml 中配置:

system:
  esign:
    app-id: 7439093682  # e签宝应用ID
    app-secret: da96bb269786338c156050cfa23eba4f  # e签宝应用密钥
    api-url: https://smlopenapi.esign.cn  # API地址(沙箱环境)
    # api-url: https://openapi.esign.cn  # 生产环境
    enabled: true

常见问题

1. Token过期问题

问题: API调用返回401或token过期错误

解决:

  • Token有效期为2小时,系统会自动刷新
  • 如果出现问题,可以调用 EsignSdkUtil.clearCachedToken() 清除缓存,重新获取

2. 手写签署不显示

问题: 签署链接打开后没有手写签署选项

解决:

  • 确保 signFieldStyle=1(手写签名)
  • 确保 signFieldType=5(签名类型)
  • 确保流程已启动(调用start接口)

3. 签署位置不正确

问题: 手写签署区域位置不对

解决:

  • 调整 xywidthheight 参数
  • 注意坐标是从PDF左上角开始计算的像素值
  • 建议先在e签宝工作台测试签署位置

参考资料