通知安全机制

由于文档签署涉及大量数字签名、文档操作等耗时操作,签署任务的执行采用异步方式进行,完成后通过HTTP协议POST请求回调的方式通知平台方签署结果。

URL:
POST https://ip.port/callback

HEADER:
Content-Type:application/json

BODY :
{
    "action":"SIGN_FLOW_UPDATE",
    "flowId":"11111113a466442abbce094c9368ac7c",
    "accountId":"222222203314483b6812498a5b61e2a",
    "authorizedAccountId":"2222222203314483b6812498a5b61e2a",
    "signTime":"2019-07-24 19:33:06",
    "order":1,
    "signResult":2,
    "thirdOrderNo":"0001,002",
    "resultDescription":"签署完成",
    "timestamp":1563967986960
}

e签宝为了保证通知的安全,系统提供2种方式供用户选择:IP白名单模式和签名验签模式。 两种模式可以单独使用,也可以共同使用。

一、IP白名单模式

为防止回调接口被攻击,可使用IP白名单的机制进行访问限制。

所有e签宝系统回调通知的IP地址:

测试环境:47.96.79.204
正式环境:118.31.35.8

以下以Nginx服务器为例,介绍如何配置e签宝白名单

1、新建白名单 ip_white.conf

新建文件ip_white.conf,内容如下:

47.96.79.204;
118.31.35.8;
2、nginx.conf 配置示例

geo IP的白名单设置

geo $remote_addr $ip_whitelist {
    default 0;
    include ip_white.conf;
}
location /console {
    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For;
    $proxy_add_x_forwarded_for;
    if ( $ip_whitelist = 1 ) {
        proxy_pass http://127.0.0.1:8000;
        break;
    }
    return 403;
}
3、java获取回调请求IP示例
public static String getClientIp(HttpServletRequest request) {
        String ip = request.getHeader("x-forwarded-for");
        if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
            // 多次反向代理后会有多个ip值,第一个ip才是真实ip
            if( ip.indexOf(",")!=-1 ){
                ip = ip.split(",")[0];
            }
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

二、签名验签模式

在回调客户服务时,在请求header里面系统新增四个参数

参数名称 说明 参数类型
X-Tsign-Open-App-Id 客户发生业务时的项目ID header
X-Tsign-Open-SIGNATURE 签名值 header
X-Tsign-Open-TIMESTAMP 时间戳 header
X-Tsign-Open-SIGNATURE-ALGORITHM 使用的算法, 默认算法hmac-sha256 header

以算法hmac-sha256签名验证做说明,验签的数据有四部分

  • 1、时间戳

回调header的X-Tsign-Open-TIMESTAMP

  • 2、query请求的数据

客户设置的回调地址可能包含query数据,例如callback?accountId=aaa&orderNo=001。(e签宝平台不会追加任何参数)

  • 3、body 数据

即通知实际内容,按照整体的字节流来处理

  • 4、项目ID对应的密钥

客户在e签宝开放平台生成项目ID时对应的密钥

Java示例代码如下:

//签名值
string signture = request.getHeader("X-Tsign-Open-SIGNATURE");
//项目ID对应的密钥
string appSecret = "10d1282521f395f36bda5a6d34603413";

//1. 获取时间戳的字节流
string timeStamp = request.getHeader("X-Tsign-Open-TIMESTAMP");
byte[] timeStampBytes = timeStamp.getBytes("UTF_8");

//2. 获取query请求的字节流
//对 Query 参数按照字典对 Key 进行排序后,按照value1+value2方法拼接
List<string> requestParamKeys = request.getParameterNames().list();
Collections.sort(requestParamKeys);
string requestQuery = "";
for (string key : requestParamKeys) {
     string value = request.getParameter(key);
     requestQuery += value == null ? "" : value;
}
byte[] requestQueryBytes = requestQuery.getBytes("UTF_8");

//3. 获取body的数据
byte[] requestBodyBytes = request.getInputStream().toByteArray();

//4. 组装数据并计算签名
byte[] data = timeStampBytes + requestQueryBytes + requestBodyBytes;
string mySign = HMACSHA256.sha256_HMAC(data,appSecret.getBytes("UTF_8"));

if(mySign.equals(signture)){
    print("签名成功");
}
1、java计算HMACSHA256加密示例

package com.lh.micro.datasource.util;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class HMACSHA256 {


    /**
     * 将加密后的字节数组转换成字符串
     *
     * @param b 字节数组
     * @return 字符串
     */
    public  static String byteArrayToHexString(byte[] b) {
        StringBuilder hs = new StringBuilder();
        String stmp;
        for (int n = 0; b!=null && n < b.length; n++) {
            stmp = Integer.toHexString(b[n] & 0XFF);
            if (stmp.length() == 1)
                hs.append('0');
            hs.append(stmp);
        }
        return hs.toString().toLowerCase();
    }
    /**
     * sha256_HMAC加密
     * @param message 消息
     * @param secret  秘钥
     * @return 加密后字符串
     */
    public static String sha256_HMAC(String message, String secret) {
        String hash = "";
        try {
            Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
            SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
            sha256_HMAC.init(secret_key);
            byte[] bytes = sha256_HMAC.doFinal(message.getBytes());
            hash = byteArrayToHexString(bytes);
        } catch (Exception e) {
            System.out.println("Error HmacSHA256 ===========" + e.getMessage());
        }
        return hash;
    }

results matching ""

    No results matching ""