通知安全机制
由于文档签署涉及大量数字签名、文档操作等耗时操作,签署任务的执行采用异步方式进行,完成后通过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;
}