diff --git a/server/dfp-open-sdk/pom.xml b/server/dfp-open-sdk/pom.xml
new file mode 100644
index 00000000..51d53a2c
--- /dev/null
+++ b/server/dfp-open-sdk/pom.xml
@@ -0,0 +1,49 @@
+
+
+
+
+ org.mdd
+ likeadmin-java
+ 1.0.0
+
+
+ 4.0.0
+ dfp-open-sdk
+ jar
+ dfp-open-sdk
+ 兴业数金开放银行平台 Java SDK(源码由银行 demo 迁入,版本参见 demo Readme)
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+
+ com.alibaba
+ fastjson
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.10.0
+
+ 1.8
+ 1.8
+ ${project.build.sourceEncoding}
+
+
+
+
+
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/Example.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/Example.java
new file mode 100644
index 00000000..dc1c77dc
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/Example.java
@@ -0,0 +1,230 @@
+package com.cib.fintech.dfp.open.sdk;
+
+import com.cib.fintech.dfp.open.sdk.config.Configure;
+import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
+import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
+import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
+import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 示例类
+ *
+ * 注意:该类仅供参考学习,请不要将该类直接包含在开发者代码中
+ * 重要:各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,务必保证传入参数的安全性,否则会导致安全问题
+ */
+public class Example {
+
+ public static void main(String[] args) {
+ Example example = new Example();
+
+ // 【1.设置SDK中应用密钥信息,需在项目启动时完成赋值】
+ example.keyConfigureInit();
+
+ // 设置是否测试环境,true连接测试环境,false连接生产环境
+ Configure.setDevEnv(true);
+
+ // 按需求调用对应方法,传入keyId区分应用配置
+ String response = example.execSdk("KYONLYFORTEST012345678");
+
+ System.out.println(response);
+ }
+
+ /**
+ * 设置SDK中应用密钥信息
+ *
+ * 注意:需在项目启动时完成赋值
+ * 本代码值仅为示例,实际使用请替换为开发者申请的keyId及密钥
+ */
+ public void keyConfigureInit() {
+
+ Map keyConfigures = Configure.getKeyConfigures();
+
+ // 应用ID,为KY字母开头的字符串,区分大小写
+ String keyId = "KYONLYFORTEST012345678";
+
+ // 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
+ String priKey = "AK7JUorRStFAwM1+BXiHefwmCOZXjVU42b6Oa5JFf5Y7";
+
+ // 响应报文验签公钥
+ String respPubKey = "BAQl3uq+FxAGi1/vAZpWyBhdTqBUcenUm8jdPl59XHOeyFkSU3obseTJOTms7lfl0rFALHVTrOmV39N97Z2ZOVI=";
+
+ // 字段加密密钥
+ String reqParamEncryptKey = "9A9A4QyNz/pc8kZZXzhXOQ==";
+
+ // keySignTypeEnum 请求签名算法
+ // respSignSwitch 响应报文验签开关,true将进行验签,false不进行验签
+ // respSignAlgorithmEnum 响应报文验签算法
+ KeyConfigure keyConfigure = new KeyConfigure();
+ keyConfigure.setKeyId(keyId);
+ keyConfigure.setPriKey(priKey);
+ keyConfigure.setRespPubKey(respPubKey);
+ keyConfigure.setReqParamEncryptKey(reqParamEncryptKey);
+ keyConfigure.setKeySignType(KeySignTypeEnum.SM3WITHSM2);
+ keyConfigure.setRespSignSwitch(true);
+ keyConfigure.setRespSignAlgorithm(RespSignAlgorithmEnum.SM3WITHSM2);
+
+ // 灰度发布配置
+ // GrayConfigure grayConfigure = new GrayConfigure();
+ // grayConfigure.setKey("wkroute");
+ // grayConfigure.setValue("graytest");
+ // keyConfigure.setGrayConfigure(grayConfigure);
+
+ // CFCA证书配置(设置CFCA私钥证书全路径文件名、私钥保护密码以及签名算法)
+ // keyConfigure
+ // .setCfcaProperties("C:/Users/cibfintech/Desktop/test.pfx", "WjXcgOQr", KeySignTypeEnum
+ // .SHA256WITHRSA);
+
+ // 用户token认证源为“企业客户授权认证”时配置
+// keyConfigure.setEnterpriseBearer("1117", "PD4gW2jjoeDzeEdwLbPMFGTN", "123456789");
+
+ keyConfigures.put(keyId, keyConfigure);
+
+ Configure.setKeyConfigures(keyConfigures);
+ }
+
+ /**
+ * 通用请求接口(非文件)
+ *
+ * @param keyId 应用ID
+ * @return 响应报文
+ */
+ public String execSdk(String keyId) {
+
+ // 【2.准备请求参数】
+ // 开发者根据API文档,准备不同的参数集合。headParams-头部信息请求参数、urlParams-URL请求参数、bodyParams-BODY请求参数
+ Map bodyParams = new HashMap(8);
+ bodyParams.put("stringParam", "Hello World");
+ bodyParams.put("intParam", "111");
+ bodyParams.put("byteParam", "1");
+ bodyParams.put("shortParam", "1111");
+ bodyParams.put("longParam", "111111111");
+ bodyParams.put("floatParam", "111.111");
+ bodyParams.put("doubleParam", "111.111111");
+ bodyParams.put("boolParam", "false");
+ bodyParams.put("charParam", "A");
+
+ bodyParams.put("mockBean[0].intParam", "111");
+ bodyParams.put("mockBean[0].stringParam", "ascb");
+
+ bodyParams.put("stringList[0]", "stringList0");
+ bodyParams.put("stringList[1]", "stringList1");
+
+ bodyParams.put("mockBeanMap[0].intParam", "111");
+ bodyParams.put("mockBeanMap[0].doubleParam", "111.111");
+ bodyParams.put("mockBeanMap[0].stringParam", "AAAAA");
+ bodyParams.put("mockBeanMap[1].intParam", "222");
+ bodyParams.put("mockBeanMap[1].doubleParam", "222.222");
+ bodyParams.put("mockBeanMap[1].stringParam", "BBBBB");
+
+ bodyParams.put("mockBeanList[0].intParam", "111");
+ bodyParams.put("mockBeanList[0].doubleParam", "111.111");
+ bodyParams.put("mockBeanList[0].stringParam", "group1/M00/97/68/CmAFGVpd3CGAREPDAAAECO1Nr3I123.txt");
+ bodyParams.put("mockBeanList[1].intParam", "222");
+ bodyParams.put("mockBeanList[1].doubleParam", "222.222");
+ bodyParams.put("mockBeanList[1].stringParam", "group2/M00/97/68/CmAFGVpd3CGAREPDAAAECO1Nr3I123.txt");
+
+ // 需要加密的字段值处理示例(请参阅对应接口文档,并按实际情况使用)
+ // try {
+ // bodyParams.put("key", SymmetricEncrypt.aesEncrypt("value","9A9A4QyNz/pc8kZZXzhXOQ=="));
+ // } catch (Exception e) {
+ // // 如果字段加密密钥或字段值设置错误,需要处理情况
+ // e.printStackTrace();
+ // }
+
+ // 【3.调用SDK方法】
+ // 传入请求资源URI、请求方法,以及各位置的参数
+ String response = OpenSdk.gatewayWithKeyId("/api/testApi", ReqMethodEnum.POST, null, null, bodyParams, keyId);
+
+ return response;
+ }
+
+ /**
+ * 通用请求接口(非文件),json
+ *
+ * @param keyId 应用ID
+ * @return 响应报文
+ */
+ public String execJsonSdk(String keyId) {
+
+ // 【2.准备请求参数】
+ // 开发者根据API文档,准备不同的参数集合。headParams-头部信息请求参数、urlParams-URL请求参数、bodyJson-BODY请求参数JSON字符串
+ String bodyJson = "{\"key\":\"value\"}";
+
+ // 需要加密的字段值处理示例(请参阅对应接口文档,并按实际情况使用)
+ // try {
+ // JSONObject bodyJson = new JSONObject();
+ // bodyJson.put("key", SymmetricEncrypt.aesEncrypt("value","9A9A4QyNz/pc8kZZXzhXOQ=="));
+ // } catch (Exception e) {
+ // // 如果字段加密密钥或字段值设置错误,需要处理情况
+ // e.printStackTrace();
+ // }
+
+ // 【3.调用SDK方法】
+ // 传入请求资源URI、请求方法,以及各位置的参数
+ String response = OpenSdk.gatewayJsonWithKeyId("/api/testApi", ReqMethodEnum.POST, null, null, bodyJson, keyId);
+
+ return response;
+ }
+
+ /**
+ * 文件下载接口
+ *
+ * 该请求用于下载文件,filePath 文件存储路径
+ *
+ * @param keyId 应用ID
+ * @return 响应报文
+ */
+ public String downloadFile(String keyId) {
+
+ // 【2.准备请求参数】
+ // 文件ID
+ String fileId = "group1/M00/A2/19/CmAFGVppoA2AII5mAAAECO1Nr3I.a914b6";
+
+ // 文件存储路径
+ String filePath = "C:\\Users\\cibfintech\\Desktop\\test.txt";
+
+ // 【3.调用SDK方法】
+ String response = OpenSdk.downloadFileWithKeyId(fileId, filePath, keyId);
+
+ return response;
+ }
+
+ /**
+ * 文件上传接口
+ *
+ * 该请求用于上传文件,filePath 文件路径
+ *
+ * @param keyId 应用ID
+ * @return 响应报文
+ */
+ public String uploadFile(String keyId) {
+
+ // 【2.准备请求参数】
+ // 待上传文件路径
+ String filePath = "C:\\Users\\cibfintech\\Desktop\\test.txt";
+
+ // 【3.调用SDK方法】
+ String response = OpenSdk.uploadFileWithKeyId(filePath, keyId);
+
+ return response;
+ }
+
+ /**
+ * 公钥查询接口
+ *
+ * 该请求用于查询公钥(如遇验签失败问题,可尝试调用该接口用于排除密钥、算法不一致问题)
+ *
+ * @param keyId 应用ID
+ * @return 响应报文
+ */
+ public String queryPubKey(String keyId) {
+
+ // 【调用SDK方法】
+ String response = OpenSdk.gatewayWithKeyId("/api/open/queryPubKey", ReqMethodEnum.GET, null, null, null, keyId);
+
+ return response;
+ }
+}
\ No newline at end of file
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/OpenSdk.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/OpenSdk.java
new file mode 100644
index 00000000..cd8162ac
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/OpenSdk.java
@@ -0,0 +1,385 @@
+package com.cib.fintech.dfp.open.sdk;
+
+import com.cib.fintech.dfp.open.sdk.config.Configure;
+import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
+import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+import com.cib.fintech.dfp.open.sdk.service.AbstractTransService;
+import com.cib.fintech.dfp.open.sdk.util.Const;
+import com.cib.fintech.dfp.open.sdk.util.FileUtil;
+import com.cib.fintech.dfp.open.sdk.util.SdkUtil;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 开放银行平台统一接口类
+ *
+ * 注意:需要先配置Configure类中的值,示例参见Example.java文件
+ * 该SDK中代码仅供学习和参考,开发者可以自己根据接口文档进行编码实现,而不使用该SDK中的实现
+ * 各字段长度、类型等请参考具体接口文档中的定义,SDK不检查这些长度,而直接发送给服务端
+ * 重要:各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,否则可能会导致安全问题
+ *
+ * @author zengminghua
+ */
+public class OpenSdk {
+
+ /**
+ * 文件ID
+ */
+ private static final String FILE_ID = "fileId";
+
+ /**
+ * 文件特征值算法
+ */
+ private static final String ALGORITHM = "SHA-1";
+
+ /**
+ * 文件哈希值key
+ */
+ private static final String FILE_HASH_VALUE = "fileHashValue";
+
+ /**
+ * SDK请求入口
+ *
+ * [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param headParams 头部信息请求参数
+ * @param urlParams URL请求参数
+ * @param bodyParams BODY请求参数
+ * @return 请求结果
+ */
+ public static String gateway(String reqUri, ReqMethodEnum reqMethod, Map headParams,
+ Map urlParams, Map bodyParams) {
+ try {
+ Object result = AbstractTransService.getInstance()
+ .exec(reqUri, reqMethod, headParams, urlParams, bodyParams, null, Configure.getKeyConfigure(),
+ null);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,form格式
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param headParams 头部信息请求参数
+ * @param urlParams URL请求参数
+ * @param bodyParams BODY请求参数
+ * @param keyId 应用ID
+ * @return 请求结果
+ */
+ public static String gatewayWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map headParams,
+ Map urlParams, Map bodyParams, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ Object result = AbstractTransService.getInstance()
+ .exec(reqUri, reqMethod, headParams, urlParams, bodyParams, null, keyConfigure, null);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,json格式
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param headParams 头部信息请求参数
+ * @param urlParams URL请求参数
+ * @param bodyJson BODY请求参数JSON字符串
+ * @param keyId 应用ID
+ * @return 请求结果
+ */
+ public static String gatewayJsonWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map headParams,
+ Map urlParams, String bodyJson, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ if (!SdkUtil.isJSONString(bodyJson)) {
+ return new SdkException(SdkExType.PARAM_ERR).toString();
+ }
+ Object result = AbstractTransService.getInstance()
+ .exec(reqUri, reqMethod, headParams, urlParams, null, null, keyConfigure, bodyJson);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,form格式
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param headParams 头部信息请求参数
+ * @param urlParams URL请求参数
+ * @param bodyParams BODY请求参数
+ * @param keyId 应用ID
+ * @return 请求结果
+ */
+ public static String gatewayNetSignWithKeyId(String reqUri, ReqMethodEnum reqMethod, Map headParams,
+ Map urlParams, Map bodyParams, String bodyJson, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ Object result = AbstractTransService.getInstance()
+ .execNetSign(reqUri, reqMethod, null, null, bodyParams, null, keyConfigure, bodyJson);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件下载
+ *
+ * [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param fileId 文件ID
+ * @return 若下载成功,返回下载的文件的字节数组(byte[]类型)
+ */
+ public static Object downloadFile(String fileId) {
+ try {
+ Map urlParams = new HashMap(8);
+ urlParams.put(FILE_ID, fileId);
+
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null,
+ Configure.getKeyConfigure(), null);
+ /**
+ * 将下载文件byte[]类型转为String并返回
+ */
+ /*String fileString = new String((byte[]) result);
+ return fileString;*/
+ return result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件下载
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param fileId 文件ID
+ * @param keyId 应用ID
+ * @return 若下载成功,返回下载的文件的字节数组(byte[]类型)
+ */
+ public static Object downloadFileWithKeyId(String fileId, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ Map urlParams = new HashMap(8);
+ urlParams.put(FILE_ID, fileId);
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null, keyConfigure, null);
+ /**
+ * 将下载文件byte[]类型转为String并返回
+ */
+ /*String fileString = new String((byte[]) result);
+ return fileString;*/
+ return result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件下载
+ *
+ * [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param fileId 文件ID
+ * @param filePath 文件存储路径
+ * @return 若下载成功,返回下载成功json提示字符串(String类型)
+ */
+ public static String downloadFile(String fileId, String filePath) {
+ try {
+ Map urlParams = new HashMap(8);
+ urlParams.put(FILE_ID, fileId);
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null,
+ Configure.getKeyConfigure(), null);
+ if (result instanceof byte[]) {
+ FileOutputStream fos = null;
+ try {
+ File file = new File(filePath);
+ fos = new FileOutputStream(file);
+ fos.write((byte[]) result);
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (Exception e) {
+
+ }
+ }
+ }
+ return Const.FILE_SUCCESS_RESULT;
+ } else {
+ return (String) result;
+ }
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件下载
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ *
+ * @param fileId 文件ID
+ * @param filePath 文件存储路径
+ * @param keyId 应用ID
+ * @return 若下载成功,返回下载成功json提示字符串(String类型)
+ */
+ public static String downloadFileWithKeyId(String fileId, String filePath, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ Map urlParams = new HashMap(8);
+ urlParams.put(FILE_ID, fileId);
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_DOWNLOAD_URI, ReqMethodEnum.GET, null, urlParams, null, null, keyConfigure, null);
+ if (result instanceof byte[]) {
+ FileOutputStream fos = null;
+ try {
+ File file = new File(filePath);
+ fos = new FileOutputStream(file);
+ fos.write((byte[]) result);
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ } finally {
+ if (fos != null) {
+ try {
+ fos.close();
+ } catch (Exception e) {
+
+ }
+ }
+ }
+ return Const.FILE_SUCCESS_RESULT;
+ } else {
+ return (String) result;
+ }
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件上传
+ *
+ * [重要]单应用配置方式,调用此方法前,请确保已经配置了Configure类中的变量,如keyId等
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ * [重要]SDK不校验文件大小,开发者请根据实际情况自行限制
+ *
+ * @param filePath 文件路径
+ * @return 文件ID
+ */
+ public static String uploadFile(String filePath) {
+ try {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ }
+ // 计算文件的哈希值
+ String fileHashValue = FileUtil.fileHash(filePath, ALGORITHM);
+ Map bodyParams = new HashMap(8);
+ bodyParams.put(FILE_HASH_VALUE, fileHashValue);
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_UPLOAD_URI, ReqMethodEnum.POST, null, null, bodyParams, filePath,
+ Configure.getKeyConfigure(), null);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+
+ /**
+ * SDK请求入口,文件上传
+ *
+ * [重要]多应用配置方式,调用此方法前,请确保已经配置了Configure类中的keyConfigures
+ * [重要]各传入参数SDK都不作任何检查、过滤,请务必在传入前进行安全检查或过滤,保证传入参数的安全性。
+ * [重要]SDK不校验文件大小,开发者请根据实际情况自行限制
+ *
+ * @param filePath 文件路径
+ * @param keyId 应用ID
+ * @return 文件ID
+ */
+ public static String uploadFileWithKeyId(String filePath, String keyId) {
+ try {
+ KeyConfigure keyConfigure = Configure.getKeyConfigures().get(keyId);
+ if (null == keyConfigure) {
+ return new SdkException(SdkExType.KEY_CONFIGURE_ERR).toString();
+ }
+ File file = new File(filePath);
+ if (!file.exists()) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ }
+ // 计算文件的哈希值
+ String fileHashValue = FileUtil.fileHash(filePath, ALGORITHM);
+ Map bodyParams = new HashMap(8);
+ bodyParams.put(FILE_HASH_VALUE, fileHashValue);
+ Object result = AbstractTransService.getInstance()
+ .exec(Const.FILE_UPLOAD_URI, ReqMethodEnum.POST, null, null, bodyParams, filePath, keyConfigure,
+ null);
+ return (String) result;
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.UNKNOWN);
+ return ex.toString();
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/common/HttpsPostRequest.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/common/HttpsPostRequest.java
new file mode 100644
index 00000000..df2f2072
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/common/HttpsPostRequest.java
@@ -0,0 +1,374 @@
+package com.cib.fintech.dfp.open.sdk.common;
+
+import com.cib.fintech.dfp.open.sdk.config.Configure;
+import com.cib.fintech.dfp.open.sdk.config.GrayConfigure;
+import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
+import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+import com.cib.fintech.dfp.open.sdk.service.AbstractTransService;
+import com.cib.fintech.dfp.open.sdk.util.Const;
+import com.cib.fintech.dfp.open.sdk.util.SdkUtil;
+import com.cib.fintech.dfp.open.sdk.util.VerifyRespSignature;
+
+import javax.net.ssl.*;
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * 使用URLConnection的通讯器
+ *
+ * @author zengminghua, tangchongcan
+ */
+public class HttpsPostRequest extends AbstractTransService {
+
+ private static final int BUFFER_SIZE = 2048;
+
+ private static final String HEADER_TIMESTAMP = "Timestamp";
+
+ private static final String HEADER_SIGNATURE = "Signature";
+
+ private static final String HEADER_NONCE = "Nonce";
+
+ private static final String HTTPS = "https";
+
+ /**
+ * request头和上传文件内容的分隔符
+ */
+ private static final String BOUNDARY = "---------------------------543279124964751";
+
+ private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
+
+ private static TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
+ @Override
+ public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+
+ @Override
+ public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+ }
+
+ @Override
+ public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
+ }
+ }};
+
+ private static HostnameVerifier hv = new HostnameVerifier() {
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ return true;
+ }
+ };
+
+ private static SSLContext sc;
+ private static SSLSocketFactory sf;
+
+ static {
+ try {
+ sc = SSLContext.getInstance("SSL");
+ sc.init(null, trustAllCerts, new java.security.SecureRandom());
+ sf = sc.getSocketFactory();
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ }
+ }
+
+ private static Map SFS = new HashMap<>();
+ private static Object LOCK = new Object();
+ private static boolean SSL_INIT_STATUS = false;
+
+ private void initSSLContents() {
+ if (!SSL_INIT_STATUS) {
+ synchronized (LOCK) {
+ if (!SSL_INIT_STATUS) {
+ Map keyConfigures = Configure.getKeyConfigures();
+ for (Map.Entry entry : keyConfigures.entrySet()) {
+ String keyId = entry.getKey();
+ FileInputStream fis = null;
+ try {
+ SSLContext sslContext = SSLContext.getInstance("SSL");
+ KeyStore keyStore = KeyStore.getInstance("PKCS12");
+ fis = new FileInputStream(new File(Configure.getCertPath(), keyId + ".p12"));
+ char[] password = entry.getValue().getCertProtectionPwd() == null ?
+ null :
+ entry.getValue().getCertProtectionPwd().toCharArray();
+ keyStore.load(fis, password);
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
+ kmf.init(keyStore, password);
+ sslContext.init(kmf.getKeyManagers(), null, new SecureRandom());
+ SFS.put(keyId, sslContext.getSocketFactory());
+ } catch (Exception e) {
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+ }
+ SSL_INIT_STATUS = true;
+ }
+ }
+ }
+ }
+
+ @Override
+ public Object send(String url, ReqMethodEnum method, String authInfo, Map headParams,
+ String bodyParamString, String filePath, Map bodyParamMap, KeyConfigure keyConfigure,
+ String bodyJson) throws Exception {
+
+ // 通讯公共部分
+ URL connUrl = null;
+ HttpURLConnection conn = null;
+ Object retn = null;
+
+ // 打开URL连接
+ connUrl = new URL(url);
+ conn = (HttpURLConnection) connUrl.openConnection();
+
+ // 测试环境忽略SSL证书验证
+ if (Configure.isDevEnv() && HTTPS.equals(connUrl.getProtocol())) {
+ ignoreSSLVerify((HttpsURLConnection) conn);
+ }
+ if (Configure.getCertPath() != null) {
+ initSSLContents();
+ SSLSocketFactory sslSocketFactory = SFS.get(keyConfigure.getKeyId());
+ if (sslSocketFactory != null) {
+ ((HttpsURLConnection) conn).setSSLSocketFactory(sslSocketFactory);
+ }
+ }
+
+ // 设置HEAD信息
+ if (keyConfigure.getEnterpriseBearer() != null) {
+ authInfo = authInfo + "," + keyConfigure.getEnterpriseBearer();
+ }
+ conn.setRequestProperty(Const.POST_HEAD_KEY, authInfo);
+ if (keyConfigure.getXCfcaBasic() != null) {
+ conn.setRequestProperty("X-Cfca-Basic", keyConfigure.getXCfcaBasic());
+ }
+ if (headParams != null) {
+ for (String key : headParams.keySet()) {
+ conn.setRequestProperty(key, headParams.get(key));
+ }
+ }
+
+ // 灰度发布配置添加到请求头中
+ GrayConfigure grayConfigure = keyConfigure.getGrayConfigure();
+ if (null != grayConfigure && grayConfigure.notNull()) {
+ conn.setRequestProperty(grayConfigure.getKey(), grayConfigure.getValue());
+ }
+
+ // 响应验签相关数据
+ String timestamp = "";
+ String responseSignValue = "";
+ String responseNonce = "";
+
+ // 非文件上传
+ if (null == filePath) {
+
+ OutputStream outStream = null;
+ InputStream inStream = null;
+
+ if (SdkUtil.isJSONString(bodyJson)) {
+ bodyParamString = bodyJson;
+ conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
+ }
+
+ try {
+ switch (method) {
+ case POST:
+ conn.setRequestMethod(ReqMethodEnum.POST.toString());
+ conn.setDoOutput(true);
+ outStream = conn.getOutputStream();
+ outStream.write(bodyParamString.getBytes(Const.CHARSET));
+ outStream.flush();
+ break;
+ case PUT:
+ conn.setRequestMethod(ReqMethodEnum.PUT.toString());
+ conn.setDoOutput(true);
+ outStream = conn.getOutputStream();
+ outStream.write(bodyParamString.getBytes(Const.CHARSET));
+ outStream.flush();
+ break;
+ case DELETE:
+ conn.setRequestMethod(ReqMethodEnum.DELETE.toString());
+ conn.setDoOutput(true);
+ outStream = conn.getOutputStream();
+ outStream.write(bodyParamString.getBytes(Const.CHARSET));
+ outStream.flush();
+ break;
+ default:
+ break;
+ }
+
+ // 获取返回结果
+ int code = conn.getResponseCode();
+ if (code >= HttpURLConnection.HTTP_OK && code < HttpURLConnection.HTTP_MULT_CHOICE && code != 0) {
+ inStream = conn.getInputStream();
+ } else {
+ inStream = conn.getErrorStream();
+ }
+
+ byte[] bin = readInputStream(inStream);
+ if (APPLICATION_OCTET_STREAM.equals(conn.getContentType())) {
+ retn = bin;
+ } else {
+ retn = new String(bin, Const.CHARSET);
+ }
+
+ timestamp = conn.getHeaderField(HEADER_TIMESTAMP);
+ responseSignValue = conn.getHeaderField(HEADER_SIGNATURE);
+ responseNonce = conn.getHeaderField(HEADER_NONCE);
+ } finally {
+ if (inStream != null) {
+ inStream.close();
+ }
+ if (outStream != null) {
+ outStream.close();
+ }
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+
+ } else {
+ // 文件上传
+ try {
+ conn.setRequestMethod(ReqMethodEnum.POST.toString());
+
+ conn.setConnectTimeout(5000);
+ conn.setReadTimeout(30000);
+ conn.setDoOutput(true);
+ conn.setDoInput(true);
+ conn.setUseCaches(false);
+
+ conn.setRequestProperty("Connection", "Keep-Alive");
+ conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
+
+ OutputStream out = new DataOutputStream(conn.getOutputStream());
+
+ // text
+ if (null != bodyParamMap) {
+ StringBuffer strBuf = new StringBuffer();
+ Iterator> iter = bodyParamMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry entry = iter.next();
+ String inputName = entry.getKey();
+ String inputValue = entry.getValue();
+ if (inputValue == null) {
+ continue;
+ }
+ strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
+ strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"\r\n\r\n");
+ strBuf.append(inputValue);
+ }
+ out.write(strBuf.toString().getBytes());
+ }
+
+ // file
+ File file = new File(filePath);
+ String filename = file.getName();
+ String inputName = "file";
+
+ StringBuffer strBuf = new StringBuffer();
+ strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
+ strBuf.append("Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename
+ + "\"\r\n\r\n");
+
+ out.write(strBuf.toString().getBytes());
+ FileInputStream fileInputStream = new FileInputStream(file);
+ DataInputStream in = new DataInputStream(fileInputStream);
+ int bytes = 0;
+ byte[] bufferOut = new byte[1024];
+ while ((bytes = in.read(bufferOut)) != -1) {
+ out.write(bufferOut, 0, bytes);
+ }
+ in.close();
+ fileInputStream.close();
+
+ byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
+ out.write(endData);
+ out.flush();
+ out.close();
+
+ // 获取返回结果
+ InputStream inStream = null;
+ int code = conn.getResponseCode();
+ if (code >= HttpURLConnection.HTTP_OK && code < HttpURLConnection.HTTP_MULT_CHOICE && code != 0) {
+ inStream = conn.getInputStream();
+ } else {
+ inStream = conn.getErrorStream();
+ }
+
+ // 读取返回数据
+ strBuf = new StringBuffer();
+ InputStreamReader inputStreamReader = new InputStreamReader(inStream);
+ BufferedReader reader = new BufferedReader(inputStreamReader);
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ strBuf.append(line);
+ }
+ retn = strBuf.toString();
+ reader.close();
+ reader = null;
+ inputStreamReader.close();
+ inputStreamReader = null;
+
+ timestamp = conn.getHeaderField(HEADER_TIMESTAMP);
+ responseSignValue = conn.getHeaderField(HEADER_SIGNATURE);
+ responseNonce = conn.getHeaderField(HEADER_NONCE);
+ } catch (FileNotFoundException e) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ } catch (IOException e) {
+ SdkException ex = new SdkException(SdkExType.CONN_ERR);
+ return ex.toString();
+ } finally {
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ }
+
+ // 响应报文验签开关非关闭,且响应报文不包含OPEN异常时,执行验签
+ if (keyConfigure.isRespSignSwitch() && !retn.toString().contains(Const.RESP_EXCEPTION_CODE)) {
+ VerifyRespSignature.verify(timestamp, responseNonce, responseSignValue, retn, keyConfigure);
+ }
+
+ return retn;
+ }
+
+ private static byte[] readInputStream(InputStream inStream) throws IOException {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ byte[] data = null;
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int len = 0;
+ try {
+ while ((len = inStream.read(buffer)) != -1) {
+ outStream.write(buffer, 0, len);
+ }
+ data = outStream.toByteArray();
+ } finally {
+ if (outStream != null) {
+ outStream.close();
+ }
+ }
+ return data;
+ }
+
+ private static void ignoreSSLVerify(HttpsURLConnection conn) {
+ conn.setSSLSocketFactory(sf);
+ conn.setHostnameVerifier(hv);
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/Configure.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/Configure.java
new file mode 100644
index 00000000..72f29d43
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/Configure.java
@@ -0,0 +1,250 @@
+package com.cib.fintech.dfp.open.sdk.config;
+
+import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
+import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 开放银行平台配置类
+ *
+ * 这里存放所有配置,开发者可以扩展该类
+ * 注意:请根据采用的配置方式调用OpenSdk类中对应的方法
+ * 1.采用单应用配置方式时,请务必配置该类中的变量,如keyId等
+ * 2.采用多应用配置方式时,请务必配置该类中的keyConfigures
+ *
+ * @author zengminghua
+ */
+public class Configure {
+
+ /********************************************** ↓ 开发者不可修改 ↓ *************************************/
+
+ /**
+ * 开放银行平台生产环境地址,无需修改
+ */
+ public static final String PROD_URL = "https://open.cibfintech.com";
+
+ /*********************************************** ↓ 开发者可修改 ↓ **************************************/
+
+ /**
+ * 是否测试环境开关,true连接测试环境,false连接生产环境
+ */
+ private static boolean devEnv = true;
+
+ /**
+ * 开放银行平台测试环境地址,可根据需要修改
+ */
+ private static String devUrl = "https://open.test.cibfintech.com";
+
+ /**
+ * 应用ID,为KY字母开头的字符串,区分大小写
+ */
+ private static String keyId = "KYONLYFORTEST012345678";
+
+ /**
+ * 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
+ */
+ private static String priKey = "AK7JUorRStFAwM1+BXiHefwmCOZXjVU42b6Oa5JFf5Y7";
+
+ /**
+ * 响应报文验签公钥
+ */
+ private static String respPubKey = "BAQl3uq+FxAGi1/vAZpWyBhdTqBUcenUm8jdPl59XHOeyFkSU3obseTJOTms7lfl0rFALHVTrOmV39N97Z2ZOVI=";
+
+ /**
+ * 字段加密密钥
+ */
+ private static String reqParamEncryptKey = "9A9A4QyNz/pc8kZZXzhXOQ==";
+
+ /**
+ * 请求签名算法
+ */
+ private static KeySignTypeEnum keySignType = KeySignTypeEnum.SM3WITHSM2;
+
+ /**
+ * 响应报文验签开关,true将进行验签,false不进行验签
+ */
+ private static boolean respSignSwitch = false;
+
+ /**
+ * 响应报文验签算法
+ */
+ private static RespSignAlgorithmEnum respSignAlgorithm = RespSignAlgorithmEnum.SM3WITHSM2;
+
+ /**
+ * HTTP通讯类,如需使用自己的类库,请修改该值
+ */
+ private static String httpsRequestClassName = "com.cib.fintech.dfp.open.sdk.common.HttpsPostRequest";
+
+ /**
+ * 多应用配置方式时,创建KeyConfigure对象存入map中。示例:map{key=keyId,value=keyConfigure}
+ */
+ private static Map keyConfigures = new HashMap<>(8);
+
+ /**
+ * 双向认证证书文件存放路径,不为空将开启双向认证
+ * 该路径下证书文件名格式要求:{keyId}.p12,如KYONLYFORTEST012345678.p12
+ */
+ private static String certPath = null;
+
+ /**
+ * 双向认证证书私钥的保护密码,用于提取证书私钥。双向认证开启时必须设置该项
+ */
+ private static String certProtectionPwd = null;
+
+ /**
+ * 灰度发布标识参数
+ */
+ private static GrayConfigure grayConfigure = null;
+
+ /*****************************************************************************************/
+
+ private static final Object GET_KEYCONFIGURE_LOCK = new Object();
+
+ public static String getKeyId() {
+ return keyId;
+ }
+
+ public static void setKeyId(String keyId) {
+ Configure.keyId = keyId;
+ }
+
+ public static String getPriKey() {
+ if (priKey.startsWith("-----BEGIN PRIVATE KEY-----")) {
+ priKey = priKey.replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", "");
+ }
+ return priKey;
+ }
+
+ public static void setPriKey(String priKey) {
+ Configure.priKey = priKey;
+ }
+
+ public static KeySignTypeEnum getKeySignType() {
+ return keySignType;
+ }
+
+ public static void setKeySignType(KeySignTypeEnum keySignType) {
+ Configure.keySignType = keySignType;
+ }
+
+ public static boolean isRespSignSwitch() {
+ return respSignSwitch;
+ }
+
+ public static void setRespSignSwitch(boolean respSignSwitch) {
+ Configure.respSignSwitch = respSignSwitch;
+ }
+
+ public static RespSignAlgorithmEnum getRespSignAlgorithm() {
+ return respSignAlgorithm;
+ }
+
+ public static void setRespSignAlgorithm(RespSignAlgorithmEnum respSignAlgorithm) {
+ Configure.respSignAlgorithm = respSignAlgorithm;
+ }
+
+ public static String getRespPubKey() {
+ return respPubKey;
+ }
+
+ public static void setRespPubKey(String respPubKey) {
+ Configure.respPubKey = respPubKey;
+ }
+
+ public static String getReqParamEncryptKey() {
+ return reqParamEncryptKey;
+ }
+
+ public static void setReqParamEncryptKey(String reqParamEncryptKey) {
+ Configure.reqParamEncryptKey = reqParamEncryptKey;
+ }
+
+ public static String getHttpsRequestClassName() {
+ return httpsRequestClassName;
+ }
+
+ public static void setHttpsRequestClassName(String httpsRequestClassName) {
+ Configure.httpsRequestClassName = httpsRequestClassName;
+ }
+
+ public static boolean isDevEnv() {
+ return devEnv;
+ }
+
+ public static void setDevEnv(boolean devEnv) {
+ Configure.devEnv = devEnv;
+ }
+
+ public static String getDevUrl() {
+ return devUrl;
+ }
+
+ public static void setDevUrl(String devUrl) {
+ Configure.devUrl = devUrl;
+ }
+
+ public static Map getKeyConfigures() {
+ return keyConfigures;
+ }
+
+ public static void setKeyConfigures(Map keyConfigures) {
+ Configure.keyConfigures = keyConfigures;
+ }
+
+ public static String getCertPath() {
+ return certPath;
+ }
+
+ public static void setCertPath(String certPath) {
+ Configure.certPath = certPath;
+ }
+
+ public static String getCertProtectionPwd() {
+ return certProtectionPwd;
+ }
+
+ public static void setCertProtectionPwd(String certProtectionPwd) {
+ Configure.certProtectionPwd = certProtectionPwd;
+ }
+
+ public static GrayConfigure getGrayConfigure() {
+ return grayConfigure;
+ }
+
+ public static void setGrayConfigure(GrayConfigure grayConfigure) {
+ Configure.grayConfigure = grayConfigure;
+ }
+
+ /**
+ * 采用单应用配置方式时,获取配置对象方法
+ *
+ * @return keyConfigure
+ */
+ public static KeyConfigure getKeyConfigure() {
+ KeyConfigure keyConfigure = keyConfigures.get(keyId);
+ if (null != keyConfigure) {
+ return keyConfigure;
+ }
+ synchronized (GET_KEYCONFIGURE_LOCK) {
+ keyConfigure = keyConfigures.get(keyId);
+ if (null != keyConfigure) {
+ return keyConfigure;
+ }
+ keyConfigure = new KeyConfigure();
+ keyConfigure.setKeyId(keyId);
+ keyConfigure.setPriKey(priKey);
+ keyConfigure.setKeySignType(keySignType);
+ keyConfigure.setRespSignSwitch(respSignSwitch);
+ keyConfigure.setRespSignAlgorithm(respSignAlgorithm);
+ keyConfigure.setRespPubKey(respPubKey);
+ keyConfigure.setReqParamEncryptKey(reqParamEncryptKey);
+ keyConfigure.setCertProtectionPwd(certProtectionPwd);
+ keyConfigure.setGrayConfigure(grayConfigure);
+ keyConfigures.put(keyId, keyConfigure);
+ }
+ return keyConfigure;
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/GrayConfigure.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/GrayConfigure.java
new file mode 100644
index 00000000..c78a6bef
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/GrayConfigure.java
@@ -0,0 +1,50 @@
+package com.cib.fintech.dfp.open.sdk.config;
+
+/**
+ * 灰度发布配置对象
+ *
+ * @author zengminghua
+ */
+public class GrayConfigure {
+
+ /**
+ * 请求头中的灰度参数键
+ */
+ private String key;
+
+ /**
+ * 请求头中的灰度参数值
+ */
+ private String value;
+
+ public GrayConfigure() {
+ }
+
+ public GrayConfigure(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ this.key = key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public boolean notNull() {
+ if (null != this.key && null != this.value) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/KeyConfigure.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/KeyConfigure.java
new file mode 100644
index 00000000..58594f6a
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/config/KeyConfigure.java
@@ -0,0 +1,232 @@
+package com.cib.fintech.dfp.open.sdk.config;
+
+import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
+import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
+import com.cib.fintech.dfp.open.sdk.util.Base64;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.text.SimpleDateFormat;
+import java.util.Enumeration;
+
+/**
+ * 应用配置对象
+ *
+ * @author zengminghua
+ */
+public class KeyConfigure {
+
+ /**
+ * 应用ID,为KY字母开头的字符串,区分大小写
+ */
+ private String keyId;
+
+ /**
+ * 请求报文签名私钥,需严格保密,建议使用配置文件或其他方式进行存储
+ */
+ private String priKey;
+
+ /**
+ * 响应报文验签公钥
+ */
+ private String respPubKey;
+
+ /**
+ * 字段加密密钥
+ */
+ private String reqParamEncryptKey;
+
+ /**
+ * 请求签名算法
+ */
+ private KeySignTypeEnum keySignType = KeySignTypeEnum.SM3WITHSM2;
+
+ /**
+ * 响应报文验签开关,true将进行验签,false不进行验签
+ */
+ private boolean respSignSwitch = false;
+
+ /**
+ * 响应报文验签算法
+ */
+ private RespSignAlgorithmEnum respSignAlgorithm = RespSignAlgorithmEnum.SM3WITHSM2;
+
+ /**
+ * 双向认证证书私钥的保护密码,用于提取证书私钥。双向认证开启时必须设置该项
+ */
+ private String certProtectionPwd = null;
+
+ /**
+ * 灰度发布标识参数
+ */
+ private GrayConfigure grayConfigure = null;
+
+ /**
+ * CFCA证书基本信息头
+ */
+ private String xCfcaBasic = null;
+
+ /**
+ * 企业客户授权信息
+ */
+ private String enterpriseBearer = null;
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(String keyId) {
+ this.keyId = keyId;
+ }
+
+ public String getPriKey() {
+ return priKey;
+ }
+
+ public void setPriKey(String priKey) {
+ this.priKey = priKey;
+ }
+
+ public KeySignTypeEnum getKeySignType() {
+ return keySignType;
+ }
+
+ public void setKeySignType(KeySignTypeEnum keySignType) {
+ this.keySignType = keySignType;
+ }
+
+ public boolean isRespSignSwitch() {
+ return respSignSwitch;
+ }
+
+ public void setRespSignSwitch(boolean respSignSwitch) {
+ this.respSignSwitch = respSignSwitch;
+ }
+
+ public RespSignAlgorithmEnum getRespSignAlgorithm() {
+ return respSignAlgorithm;
+ }
+
+ public void setRespSignAlgorithm(RespSignAlgorithmEnum respSignAlgorithm) {
+ this.respSignAlgorithm = respSignAlgorithm;
+ }
+
+ public String getRespPubKey() {
+ return respPubKey;
+ }
+
+ public void setRespPubKey(String respPubKey) {
+ this.respPubKey = respPubKey;
+ }
+
+ public String getReqParamEncryptKey() {
+ return reqParamEncryptKey;
+ }
+
+ public void setReqParamEncryptKey(String reqParamEncryptKey) {
+ this.reqParamEncryptKey = reqParamEncryptKey;
+ }
+
+ public String getCertProtectionPwd() {
+ return certProtectionPwd;
+ }
+
+ public void setCertProtectionPwd(String certProtectionPwd) {
+ this.certProtectionPwd = certProtectionPwd;
+ }
+
+ public KeyConfigure() {
+ }
+
+ public KeyConfigure(String keyId, String priKey, KeySignTypeEnum keySignType, boolean respSignSwitch,
+ RespSignAlgorithmEnum respSignAlgorithm, String respPubKey, String reqParamEncryptKey,
+ String certProtectionPwd, GrayConfigure grayConfigure) {
+ this.keyId = keyId;
+ this.priKey = priKey;
+ this.keySignType = keySignType;
+ this.respSignSwitch = respSignSwitch;
+ this.respSignAlgorithm = respSignAlgorithm;
+ this.respPubKey = respPubKey;
+ this.reqParamEncryptKey = reqParamEncryptKey;
+ this.certProtectionPwd = certProtectionPwd;
+ this.grayConfigure = grayConfigure;
+ }
+
+ public GrayConfigure getGrayConfigure() {
+ return grayConfigure;
+ }
+
+ public void setGrayConfigure(GrayConfigure grayConfigure) {
+ this.grayConfigure = grayConfigure;
+ }
+
+ /**
+ * @param cfcaCertFilename CFCA证书文件名(全路径)
+ * @param cfcaProtectionPwd CFCA证书私钥的保护密码,用于提取CFCA证书私钥
+ * @param keySignType 签名算法
+ * @return
+ */
+ public void setCfcaProperties(String cfcaCertFilename, String cfcaProtectionPwd, KeySignTypeEnum keySignType) {
+ KeyStore keyStore;
+ FileInputStream fis = null;
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
+ try {
+ keyStore = KeyStore.getInstance("PKCS12");
+ fis = new FileInputStream(cfcaCertFilename);
+ char[] password = cfcaProtectionPwd == null ? null : cfcaProtectionPwd.toCharArray();
+ keyStore.load(fis, password);
+ Enumeration aliases = keyStore.aliases();
+ while (aliases.hasMoreElements()) {
+ String alias = aliases.nextElement();
+ X509Certificate x509Certificate = (X509Certificate) keyStore.getCertificate(alias);
+ StringBuilder sb = new StringBuilder();
+ sb.append(x509Certificate.getSerialNumber()).append("|");
+ sb.append(x509Certificate.getSigAlgName()).append("|");
+ sb.append(x509Certificate.getIssuerDN()).append("|");
+ sb.append(x509Certificate.getSubjectDN()).append("|");
+ sb.append(sdf.format(x509Certificate.getNotBefore())).append("-")
+ .append(sdf.format(x509Certificate.getNotAfter()));
+ this.xCfcaBasic = Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
+ PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
+ this.priKey = Base64.encode(privateKey.getEncoded());
+ this.keySignType = keySignType;
+ break;
+ }
+ } catch (Exception e) {
+
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+
+ }
+ }
+ }
+ }
+
+ public String getXCfcaBasic() {
+ return xCfcaBasic;
+ }
+
+ public String getEnterpriseBearer() {
+ return enterpriseBearer;
+ }
+
+ /**
+ * @param enterpriseId 企业ID
+ * @param productId 产品ID
+ * @param acctNo 企业账户号
+ */
+ public void setEnterpriseBearer(String enterpriseId, String productId, String acctNo) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(enterpriseId).append("&");
+ sb.append(productId).append("&");
+ sb.append(acctNo);
+ this.enterpriseBearer = "Bearer " + Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/KeySignTypeEnum.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/KeySignTypeEnum.java
new file mode 100644
index 00000000..c335b3a5
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/KeySignTypeEnum.java
@@ -0,0 +1,60 @@
+package com.cib.fintech.dfp.open.sdk.enums;
+
+/**
+ * 应用请求报文签名算法枚举
+ *
+ * @author tangchongcan, zengminghua
+ */
+public enum KeySignTypeEnum {
+
+ /**
+ * SHA256WITHRSA("2", "SHA256withRSA")
+ */
+ SHA256WITHRSA("2", "SHA256WithRSA"),
+
+ /**
+ * SM3WITHSM2("3", "SM3WithSM2")
+ */
+ SM3WITHSM2("3", "SM3WithSM2");
+
+ /**
+ * 根据编码找枚举
+ *
+ * @param code 编码
+ * @return KeyStatusEnum
+ */
+ public static KeySignTypeEnum getByCode(String code) {
+ if (code == null) {
+ return null;
+ }
+ if (values() == null) {
+ return null;
+ }
+ for (KeySignTypeEnum t : values()) {
+ if (t.getCode().equals(code)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ private final String code;
+ private final String label;
+
+ KeySignTypeEnum(String code, String label) {
+ this.code = code;
+ this.label = label;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public KeySignTypeEnum returnEnum(String persistedValue) {
+ return getByCode(persistedValue);
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/ReqMethodEnum.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/ReqMethodEnum.java
new file mode 100644
index 00000000..e417daf0
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/ReqMethodEnum.java
@@ -0,0 +1,14 @@
+package com.cib.fintech.dfp.open.sdk.enums;
+
+/**
+ * HTTP请求方法枚举
+ *
+ * @author zengminghua
+ */
+public enum ReqMethodEnum {
+
+ /**
+ * HTTP请求方法枚举
+ */
+ POST, GET, PUT, DELETE;
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/RespSignAlgorithmEnum.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/RespSignAlgorithmEnum.java
new file mode 100644
index 00000000..6f592ea6
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/enums/RespSignAlgorithmEnum.java
@@ -0,0 +1,65 @@
+package com.cib.fintech.dfp.open.sdk.enums;
+
+/**
+ * 响应报文验签算法枚举
+ *
+ * @author zengminghua
+ */
+public enum RespSignAlgorithmEnum {
+
+ /**
+ * SHA1WITHRSA("1", "SHA1withRSA")
+ */
+ SHA1WITHRSA("1", "SHA1WithRSA"),
+
+ /**
+ * SHA256WITHRSA("2", "SHA256withRSA")
+ */
+ SHA256WITHRSA("2", "SHA256WithRSA"),
+
+ /**
+ * SM3WITHSM2("3", "SM3WithSM2")
+ */
+ SM3WITHSM2("3", "SM3WithSM2");
+
+ /**
+ * 根据编码找枚举
+ *
+ * @param code 编码
+ * @return KeyStatusEnum
+ */
+ public static RespSignAlgorithmEnum getByCode(String code) {
+ if (code == null) {
+ return null;
+ }
+ if (values() == null) {
+ return null;
+ }
+ for (RespSignAlgorithmEnum t : values()) {
+ if (t.getCode().equals(code)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ private final String code;
+ private final String label;
+
+ private RespSignAlgorithmEnum(String code, String label) {
+ this.code = code;
+ this.label = label;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getLabel() {
+ return label;
+ }
+
+ public RespSignAlgorithmEnum returnEnum(String persistedValue) {
+ return getByCode(persistedValue);
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkExType.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkExType.java
new file mode 100644
index 00000000..14ca1af5
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkExType.java
@@ -0,0 +1,75 @@
+package com.cib.fintech.dfp.open.sdk.exception;
+
+/**
+ * SDK异常枚举
+ *
+ * @author zengminghua
+ */
+public enum SdkExType {
+
+ /**
+ * 异常码枚举
+ */
+ CONN_ERR("OPEN25801", "通讯错误或超时,交易未决"),
+
+ PARAM_ERR("OPEN25802", "参数校验错误"),
+
+ UNKNOWN("OPEN25803", "未知错误,请检查是否为最新版本SDK或是否配置错误"),
+
+ RESPONSE_SIGN_ERR("OPEN25804", "响应报文验签失败,请检查验签公钥或验签算法配置"),
+
+ FILE_ERROR_RESULT("OPEN25805", "文件读取或写入失败"),
+
+ REQ_PARAM_ENCRYPT_ERR("OPEN25806", "字段加/解密失败,请检查字段加密密钥或字段值"),
+
+ KEY_CONFIGURE_ERR("OPEN25807", "当前keyId未找到配置对象信息,请检查keyConfigures集合中是否保存"),
+
+ CALLBACK_REQUEST_VERIFY_ERR("OPEN25808", "回调请求报文验签失败,请检查验签公钥或验签算法配置"),
+
+ CALLBACK_RESPONSE_SIGN_ERR("OPEN25809", "回调响应报文签名失败,请检查签名私钥或签名算法配置"),
+
+ CALLBACK_BODY_ENCRYPT_ERR("OPEN25810", "回调报文加/解密失败,请检查报文加/解密钥或报文值"),
+
+ NET_SIGN_ERR("OPEN25812", "数字信封制作失败,请检查签名/验签密钥或加密算法配置");
+
+ /**
+ * 根据编码找枚举
+ *
+ * @param code 编码
+ * @return SdkExType
+ */
+ public static SdkExType getByCode(String code) {
+ if (code == null) {
+ return null;
+ }
+
+ for (SdkExType t : values()) {
+ if (t.getCode().equals(code)) {
+ return t;
+ }
+ }
+ return null;
+ }
+
+ private final String code;
+
+ private final String msg;
+
+ private SdkExType(String code, String msg) {
+ this.code = code;
+ this.msg = msg;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public SdkExType returnEnum(String persistedValue) {
+ return getByCode(persistedValue);
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkException.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkException.java
new file mode 100644
index 00000000..69376dda
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/exception/SdkException.java
@@ -0,0 +1,62 @@
+package com.cib.fintech.dfp.open.sdk.exception;
+
+/**
+ * SDK异常
+ *
+ * @author zengminghua
+ */
+public class SdkException extends Exception {
+
+ private static final long serialVersionUID = 7634266621864608219L;
+
+ /**
+ * 异常码
+ */
+ private String code;
+
+ /**
+ * 异常信息
+ */
+ private String msg;
+
+ /**
+ * 异常跟踪ID
+ */
+ private String traceId;
+
+ public SdkException(SdkExType code) {
+ this.code = code.getCode();
+ this.msg = code.getMsg();
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setCode(String code) {
+ this.code = code;
+ }
+
+ public String getMsg() {
+ return msg;
+ }
+
+ public void setMsg(String msg) {
+ this.msg = msg;
+ }
+
+ public String getTraceId() {
+ return traceId;
+ }
+
+ public void setTraceId(String traceId) {
+ this.traceId = traceId;
+ }
+
+ @Override
+ public String toString() {
+ return "{\"code\":\"" + code + "\",\"msg\":\"[" + code + "]" + msg + "\",\"traceId\":\"OPEN-00-LOCAL-" + code
+ .substring(code.length() - 3, code.length()) + "\"}";
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/service/AbstractTransService.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/service/AbstractTransService.java
new file mode 100644
index 00000000..ba324dd0
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/service/AbstractTransService.java
@@ -0,0 +1,368 @@
+package com.cib.fintech.dfp.open.sdk.service;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.cib.fintech.dfp.open.sdk.config.Configure;
+import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
+import com.cib.fintech.dfp.open.sdk.enums.KeySignTypeEnum;
+import com.cib.fintech.dfp.open.sdk.enums.ReqMethodEnum;
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+import com.cib.fintech.dfp.open.sdk.util.*;
+import com.cib.fintech.dfp.open.sdk.util.Base64;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * 通用请求器接口
+ *
+ * 如果需要使用自己的HTTP通讯库,请继承自该接口
+ *
+ * @author zengminghua
+ */
+public abstract class AbstractTransService {
+
+ /**
+ * 发送请求
+ *
+ * @param url 请求地址
+ * @param method 请求方法
+ * @param authInfo 请求头部签名信息
+ * @param headParams 头部请求参数集合
+ * @param bodyParam BODY请求参数
+ * @param filePath 文件路径
+ * @param bodyParamMap BODY请求参数集合
+ * @param keyConfigure 应用配置对象
+ * @param bodyJson BODY请求参数JSON字符串
+ * @return 通讯返回结果
+ * @throws Exception
+ */
+ public abstract Object send(String url, ReqMethodEnum method, String authInfo, Map headParams,
+ String bodyParam, String filePath, Map bodyParamMap, KeyConfigure keyConfigure,
+ String bodyJson) throws Exception;
+
+ /**
+ * 获取Configure指定的通讯类实例
+ *
+ * @return 通讯类实例
+ * @throws ClassNotFoundException
+ * @throws InstantiationException
+ * @throws IllegalAccessException
+ */
+ @SuppressWarnings("rawtypes")
+ public static AbstractTransService getInstance()
+ throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ Class c = Class.forName(Configure.getHttpsRequestClassName());
+ return (AbstractTransService) c.newInstance();
+ }
+
+ /**
+ * 通讯接口
+ *
+ * @param url 请求地址
+ * @param method 请求方法
+ * @param authInfo 请求头部签名信息
+ * @param headParams 头部请求参数
+ * @param bodyParamString BODY请求参数
+ * @param filePath 文件路径
+ * @param bodyParamMap BODY请求参数集合
+ * @param keyConfigure 应用配置对象
+ * @param bodyJson BODY请求参数JSON字符串
+ * @return 通讯返回字符串
+ */
+ protected static Object txn(String url, ReqMethodEnum method, String authInfo, Map headParams,
+ String bodyParamString, String filePath, Map bodyParamMap, KeyConfigure keyConfigure,
+ String bodyJson) throws SdkException {
+ Object resp = null;
+ try {
+ AbstractTransService transService = AbstractTransService.getInstance();
+ resp = transService
+ .send(url, method, authInfo, headParams, bodyParamString, filePath, bodyParamMap, keyConfigure,
+ bodyJson);
+ } catch (Exception e) {
+ if (e instanceof IOException) {
+ throw new SdkException(SdkExType.CONN_ERR);
+ } else if (e instanceof SdkException) {
+ throw (SdkException) e;
+ } else {
+ throw new SdkException(SdkExType.UNKNOWN);
+ }
+ }
+ return resp;
+ }
+
+ /**
+ * 获取请求地址
+ *
+ * @return
+ */
+ protected String getPostUrl() {
+ String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
+ return (SdkUtil.notBlank(url) && url.endsWith("/")) ? url.substring(0, url.length() - 1) : url;
+ }
+
+ /**
+ * 计算头部签名信息
+ *
+ * @param method 请求方法
+ * @param uri 请求URI
+ * @param signParams 参数集合
+ * @param keyConfigure 应用配置对象
+ * @return 头部认证信息内容
+ */
+ protected String getAuthInfo(ReqMethodEnum method, String uri, Map signParams,
+ KeyConfigure keyConfigure) {
+ String timestamp = SdkUtil.getDateTime();
+
+ // 待签参数拼接
+ String nonce = UUID.randomUUID().toString().replaceAll("-", "");
+ StringBuilder builder = new StringBuilder();
+ builder.append(keyConfigure.getKeyId());
+ builder.append("&");
+ builder.append(timestamp);
+ builder.append("&");
+ builder.append(nonce);
+ builder.append("&");
+ builder.append(method);
+ builder.append("&");
+ builder.append(uri);
+ if (signParams != null && signParams.size() != 0) {
+ List keyList = new ArrayList(signParams.keySet());
+ Collections.sort(keyList);
+ for (String key : keyList) {
+ builder.append("&");
+ builder.append(key);
+ builder.append("=");
+ builder.append(signParams.get(key));
+ }
+ }
+
+ // 组装请求头部签名信息
+ String user = keyConfigure.getKeyId() + "_" + timestamp + "_" + nonce;
+ String pwd;
+ if (KeySignTypeEnum.SHA256WITHRSA.equals(keyConfigure.getKeySignType())) {
+ pwd = Signature.signatureByRSA(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
+ } else {
+ pwd = Signature.signatureBySM2(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
+ }
+
+ return "Basic " + Base64.encode((user + ":" + pwd).getBytes());
+ }
+
+ /**
+ * 计算头部签名信息(数字信封)
+ *
+ * @param method 请求方法
+ * @param uri 请求URI
+ * @param signParams 参数集合
+ * @param keyConfigure 应用配置对象
+ * @return 头部认证信息内容
+ */
+ protected String getNetSignAuthInfo(ReqMethodEnum method, String uri, String commKey, Map signParams,
+ KeyConfigure keyConfigure) throws Exception {
+ String timestamp = SdkUtil.getDateTime();
+
+ // 待签参数拼接
+ String nonce = UUID.randomUUID().toString().replaceAll("-", "");
+ StringBuilder builder = new StringBuilder();
+ builder.append(keyConfigure.getKeyId());
+ builder.append("&");
+ builder.append(timestamp);
+ builder.append("&");
+ builder.append(nonce);
+ builder.append("&");
+ builder.append(method);
+ builder.append("&");
+ builder.append(uri);
+ builder.append("&");
+ builder.append(commKey);
+
+ if (signParams != null && signParams.size() != 0) {
+ List keyList = new ArrayList(signParams.keySet());
+ Collections.sort(keyList);
+ for (String key : keyList) {
+ builder.append("&");
+ builder.append(key);
+ builder.append("=");
+ builder.append(signParams.get(key));
+ }
+ }
+ String encCommKey = Signature.encryptBySM2PublicKey(keyConfigure.getRespPubKey(), commKey.getBytes(StandardCharsets.UTF_8));
+ // 组装请求头部信息
+ String user = keyConfigure.getKeyId() + "_" + timestamp + "_" + nonce + "_" + encCommKey;
+ String pwd;
+ if (KeySignTypeEnum.SHA256WITHRSA.equals(keyConfigure.getKeySignType())) {
+ pwd = Signature.signatureByRSA(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
+ } else {
+ pwd = Signature.signatureBySM2(builder.toString(), keyConfigure.getPriKey(), Const.CHARSET);
+ }
+
+ return "Basic " + Base64.encode((user + ":" + pwd).getBytes());
+ }
+
+ /**
+ * 加密请求参数
+ *
+ * @param bodyParamMap
+ * @param bodyParamJson
+ * @param commKey
+ * @return
+ */
+ public Map getEncryptBody(Map bodyParamMap, String commKey) throws SdkException {
+ String bodyParamJsonString = JSON.toJSONString(bodyParamMap);
+ Map encryptBodyParamMap = new HashMap<>(2);
+ try {
+ encryptBodyParamMap.put("ENC_DATA", SymmetricEncrypt.sm4Encrypt(bodyParamJsonString, commKey));
+ return encryptBodyParamMap;
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.NET_SIGN_ERR);
+ }
+ }
+
+ /**
+ * 执行请求处理
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param headParams 头部信息请求参数
+ * @param urlParams URL请求参数
+ * @param bodyParamMap BODY请求参数集合
+ * @param filePath 文件路径
+ * @param keyConfigure 应用配置对象
+ * @param bodyJson BODY请求参数JSON字符串
+ * @return
+ */
+ public Object exec(String reqUri, ReqMethodEnum reqMethod, Map headParams,
+ Map urlParams, Map bodyParamMap, String filePath, KeyConfigure keyConfigure,
+ String bodyJson) {
+
+ // 准备签名参数
+ Map signParams = new HashMap(64);
+ if (SdkUtil.notEmpty(headParams)) {
+ signParams.putAll(headParams);
+ }
+ if (SdkUtil.notEmpty(urlParams)) {
+ signParams.putAll(urlParams);
+ }
+ if (SdkUtil.notEmpty(bodyParamMap)) {
+ signParams.putAll(bodyParamMap);
+ }
+ if (SdkUtil.isJSONString(bodyJson)) {
+ signParams.put("BODY", bodyJson);
+ }
+
+ // 计算签名值
+ reqUri = SdkUtil.checkReqUri(reqUri);
+ String authInfo = this.getAuthInfo(reqMethod, reqUri, signParams, keyConfigure);
+
+ // 拼接请求参数
+ String urlParam = SdkUtil.jointMap(urlParams);
+ String bodyParamString = SdkUtil.jointMap(bodyParamMap);
+
+ // 拼接请求URL
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(this.getPostUrl());
+ urlBuilder.append(reqUri);
+ switch (reqMethod) {
+ case GET:
+ case POST:
+ if (SdkUtil.notBlank(urlParam)) {
+ urlBuilder.append(urlBuilder.toString().contains("?") ? "&" : "?");
+ urlBuilder.append(urlParam);
+ }
+ break;
+ default:
+ break;
+ }
+
+ // 执行通讯请求
+ Object response = "";
+ try {
+ response = txn(urlBuilder.toString(), reqMethod, authInfo, headParams, bodyParamString, filePath,
+ bodyParamMap, keyConfigure, bodyJson);
+ } catch (SdkException ex) {
+
+ if (SdkExType.CONN_ERR.getCode().equals(ex.getCode())) {
+ String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
+ response = ex.toString().replace("通讯错误", url + "通讯错误");
+ } else {
+ response = ex.toString();
+ }
+ }
+ return response;
+ }
+
+ /**
+ * 执行请求处理
+ *
+ * @param reqUri 请求资源URI
+ * @param reqMethod 请求方法
+ * @param bodyParamMap BODY请求参数集合
+ * @param filePath 文件路径
+ * @param keyConfigure 应用配置对象
+ * @param bodyJson BODY请求参数JSON字符串
+ * @return
+ */
+ public Object execNetSign(String reqUri, ReqMethodEnum reqMethod, Map headParams, Map urlParams,
+ Map bodyParamMap, String filePath, KeyConfigure keyConfigure,
+ String bodyJson) throws Exception {
+
+ // 生成通讯对称秘钥
+ String commKey = SymmetricEncrypt.generateSM4Key();
+ // 准备签名参数
+ Map signParams = new HashMap(64);
+ if (SdkUtil.notEmpty(headParams)) {
+ signParams.putAll(headParams);
+ }
+ if (SdkUtil.notEmpty(urlParams)) {
+ signParams.putAll(urlParams);
+ }
+ if (SdkUtil.notEmpty(bodyParamMap)) {
+ signParams.putAll(bodyParamMap);
+ }
+ if (SdkUtil.isJSONString(bodyJson)) {
+ signParams.put("BODY", bodyJson);
+ }
+
+ // 计算签名值
+ reqUri = SdkUtil.checkReqUri(reqUri);
+ String authInfo = this.getNetSignAuthInfo(reqMethod, reqUri, commKey, signParams, keyConfigure);
+ // 加密签名请求体
+ Map encryptBodyParamMap = this.getEncryptBody(bodyParamMap, commKey);
+
+ // 拼接请求参数
+// String bodyParamString = SdkUtil.jointMap(encryptBodyParamMap);
+
+ // 拼接请求URL
+ StringBuilder urlBuilder = new StringBuilder();
+ urlBuilder.append(this.getPostUrl());
+ urlBuilder.append(reqUri);
+ String bodyParamString = SdkUtil.jointMap(encryptBodyParamMap);
+ // 执行通讯请求
+ Object response = "";
+ try {
+ response = txn(urlBuilder.toString(), reqMethod, authInfo, null, bodyParamString, filePath,
+ encryptBodyParamMap, keyConfigure, bodyJson);
+ //响应报文解密
+ JSONObject responseObject = JSON.parseObject((String) response);
+ String encData = responseObject.getString("ENC_DATA");
+ if (encData == null || encData.equals("")) {
+ return response;
+ }
+ response = SymmetricEncrypt.sm4Decrypt(encData, commKey);
+ } catch (SdkException ex) {
+
+ if (SdkExType.CONN_ERR.getCode().equals(ex.getCode())) {
+ String url = Configure.isDevEnv() ? Configure.getDevUrl() : Configure.PROD_URL;
+ response = ex.toString().replace("通讯错误", url + "通讯错误");
+ } else {
+ response = ex.toString();
+ }
+ }
+
+ return response;
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Base64.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Base64.java
new file mode 100644
index 00000000..7091d9e5
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Base64.java
@@ -0,0 +1,293 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+/**
+ * Base64工具类
+ *
+ * @author cibfintech
+ */
+public final class Base64 {
+
+ static private final int BASELENGTH = 128;
+ static private final int LOOKUPLENGTH = 64;
+ static private final int TWENTYFOURBITGROUP = 24;
+ static private final int EIGHTBIT = 8;
+ static private final int SIXTEENBIT = 16;
+ static private final int FOURBYTE = 4;
+ static private final int SIGN = -128;
+ static private final char PAD = '=';
+ static private final boolean F_DEBUG = false;
+ static final private byte[] BASE64_ALPHABET = new byte[BASELENGTH];
+ static final private char[] LOOKUP_BASE64_ALPHABET = new char[LOOKUPLENGTH];
+
+ static {
+ for (int i = 0; i < BASELENGTH; ++i) {
+ BASE64_ALPHABET[i] = -1;
+ }
+ for (int i = 'Z'; i >= 'A'; i--) {
+ BASE64_ALPHABET[i] = (byte) (i - 'A');
+ }
+ for (int i = 'z'; i >= 'a'; i--) {
+ BASE64_ALPHABET[i] = (byte) (i - 'a' + 26);
+ }
+
+ for (int i = '9'; i >= '0'; i--) {
+ BASE64_ALPHABET[i] = (byte) (i - '0' + 52);
+ }
+
+ BASE64_ALPHABET['+'] = 62;
+ BASE64_ALPHABET['/'] = 63;
+
+ for (int i = 0; i <= 25; i++) {
+ LOOKUP_BASE64_ALPHABET[i] = (char) ('A' + i);
+ }
+
+ for (int i = 26, j = 0; i <= 51; i++, j++) {
+ LOOKUP_BASE64_ALPHABET[i] = (char) ('a' + j);
+ }
+
+ for (int i = 52, j = 0; i <= 61; i++, j++) {
+ LOOKUP_BASE64_ALPHABET[i] = (char) ('0' + j);
+ }
+ LOOKUP_BASE64_ALPHABET[62] = (char) '+';
+ LOOKUP_BASE64_ALPHABET[63] = (char) '/';
+
+ }
+
+ private static boolean isWhiteSpace(char octect) {
+ return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
+ }
+
+ private static boolean isPad(char octect) {
+ return (octect == PAD);
+ }
+
+ private static boolean isData(char octect) {
+ return (octect < BASELENGTH && BASE64_ALPHABET[octect] != -1);
+ }
+
+ /**
+ * Encodes hex octects into Base64
+ *
+ * @param binaryData Array containing binaryData
+ * @return Encoded Base64 array
+ */
+ public static String encode(byte[] binaryData) {
+
+ if (binaryData == null) {
+ return null;
+ }
+
+ int lengthDataBits = binaryData.length * EIGHTBIT;
+ if (lengthDataBits == 0) {
+ return "";
+ }
+
+ int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
+ int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
+ int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
+ char[] encodedData = null;
+
+ encodedData = new char[numberQuartet * 4];
+
+ byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
+
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ if (F_DEBUG) {
+ System.out.println("number of triplets = " + numberTriplets);
+ }
+
+ for (int i = 0; i < numberTriplets; i++) {
+ b1 = binaryData[dataIndex++];
+ b2 = binaryData[dataIndex++];
+ b3 = binaryData[dataIndex++];
+
+ if (F_DEBUG) {
+ System.out.println("b1= " + b1 + ", b2= " + b2 + ", b3= " + b3);
+ }
+
+ l = (byte) (b2 & 0x0f);
+ k = (byte) (b1 & 0x03);
+
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+ byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
+
+ if (F_DEBUG) {
+ System.out.println("val2 = " + val2);
+ System.out.println("k4 = " + (k << 4));
+ System.out.println("vak = " + (val2 | (k << 4)));
+ }
+
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val2 | (k << 4)];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[(l << 2) | val3];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[b3 & 0x3f];
+ }
+
+ // form integral number of 6-bit groups
+ if (fewerThan24bits == EIGHTBIT) {
+ b1 = binaryData[dataIndex];
+ k = (byte) (b1 & 0x03);
+ if (F_DEBUG) {
+ System.out.println("b1=" + b1);
+ System.out.println("b1<<2 = " + (b1 >> 2));
+ }
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[k << 4];
+ encodedData[encodedIndex++] = PAD;
+ encodedData[encodedIndex++] = PAD;
+ } else if (fewerThan24bits == SIXTEENBIT) {
+ b1 = binaryData[dataIndex];
+ b2 = binaryData[dataIndex + 1];
+ l = (byte) (b2 & 0x0f);
+ k = (byte) (b1 & 0x03);
+
+ byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
+ byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
+
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val1];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[val2 | (k << 4)];
+ encodedData[encodedIndex++] = LOOKUP_BASE64_ALPHABET[l << 2];
+ encodedData[encodedIndex++] = PAD;
+ }
+
+ return new String(encodedData);
+ }
+
+ /**
+ * Decodes Base64 data into octects
+ *
+ * @param encoded string containing Base64 data
+ * @return Array containind decoded data.
+ */
+ public static byte[] decode(String encoded) {
+
+ if (encoded == null) {
+ return null;
+ }
+
+ char[] base64Data = encoded.toCharArray();
+
+ /**
+ * remove white spaces
+ */
+ int len = removeWhiteSpace(base64Data);
+
+ /**
+ * should be divisible by four
+ */
+ if (len % FOURBYTE != 0) {
+ return null;
+ }
+
+ int numberQuadruple = (len / FOURBYTE);
+
+ if (numberQuadruple == 0) {
+ return new byte[0];
+ }
+
+ byte[] decodedData = null;
+ byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
+ char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
+
+ int i = 0;
+ int encodedIndex = 0;
+ int dataIndex = 0;
+ decodedData = new byte[(numberQuadruple) * 3];
+
+ for (; i < numberQuadruple - 1; i++) {
+
+ if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
+ || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) {
+ return null;
+ }// if found "no data" just return null
+
+ b1 = BASE64_ALPHABET[d1];
+ b2 = BASE64_ALPHABET[d2];
+ b3 = BASE64_ALPHABET[d3];
+ b4 = BASE64_ALPHABET[d4];
+
+ decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+ }
+
+ /**
+ * if found "no data" just return null
+ */
+ if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) {
+ return null;
+ }
+
+ b1 = BASE64_ALPHABET[d1];
+ b2 = BASE64_ALPHABET[d2];
+
+ d3 = base64Data[dataIndex++];
+ d4 = base64Data[dataIndex++];
+ /**
+ * Check if they are PAD characters
+ */
+ if (!isData((d3)) || !isData((d4))) {
+ if (isPad(d3) && isPad(d4)) {
+ /**
+ * last 4 bits should be zero
+ */
+ if ((b2 & 0xf) != 0) {
+ return null;
+ }
+ byte[] tmp = new byte[i * 3 + 1];
+ System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+ tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
+ return tmp;
+ } else if (!isPad(d3) && isPad(d4)) {
+ b3 = BASE64_ALPHABET[d3];
+ /**
+ * last 2 bits should be zero
+ */
+ if ((b3 & 0x3) != 0) {
+ return null;
+ }
+ byte[] tmp = new byte[i * 3 + 2];
+ System.arraycopy(decodedData, 0, tmp, 0, i * 3);
+ tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+ tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ return tmp;
+ } else {
+ return null;
+ }
+ } else { // No PAD e.g 3cQl
+ b3 = BASE64_ALPHABET[d3];
+ b4 = BASE64_ALPHABET[d4];
+ decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
+ decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
+ decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
+
+ }
+
+ return decodedData;
+ }
+
+ /**
+ * remove WhiteSpace from MIME containing encoded Base64 data.
+ *
+ * @param data the byte array of base64 data (with WS)
+ * @return the new length
+ */
+ private static int removeWhiteSpace(char[] data) {
+ if (data == null) {
+ return 0;
+ }
+
+ // count characters that's not whitespace
+ int newSize = 0;
+ int len = data.length;
+ for (int i = 0; i < len; i++) {
+ if (!isWhiteSpace(data[i])) {
+ data[newSize++] = data[i];
+ }
+ }
+ return newSize;
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/CallbackUtil.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/CallbackUtil.java
new file mode 100644
index 00000000..1142e420
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/CallbackUtil.java
@@ -0,0 +1,157 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 回调相关工具类
+ *
+ * @author wenjunxin
+ * @version 4.4.0
+ */
+public class CallbackUtil {
+
+ /**
+ * 对回调请求进行解密验签
+ *
+ * @param keyId 应用ID(请求头中获得,字段名:Keyid,示例:KYONLYFORTEST012345678)
+ * @param timestamp 时间戳(请求头中获得,字段名:Timestamp,示例:20210810185848 其中时间格式为yyyyMMddHHmmss)
+ * @param nonce 随机数(请求头中获得,字段名:Nonce,示例:CGYHt6YaCn7aFm8B76yXTW1Q)
+ * @param signValue 签名值(请求头中获得,字段名:Signature,示例:T3J8o4zY5eGvG4to7xkGfqTZN3Nk4w8NSKG/ADdxhFy+OhhebHUS8t4Wk+w3HSA4K2UXJA5o8AUXm/QAa98FFg==)
+ * @param body 回调请求数据(请求体中获得,参数名:ciphertext,加密内容,示例:{"ciphertext":"xq1hzkLPC4JQC6FVE7nCzroDjWK+3WtYGrYuwpgGsX4="} )
+ * @param decryptKey 解密秘钥(sm4加密,示例:iij8IGO+6+ZVpRshrug3IQ==)
+ * @param publicKey 验签公钥(sm2加密,示例:BCfPH6Alxb18eLXup8zF4nw3IZrf44FOZYSIlEaI1ePmWijbrBHiEGSWZzMcBbbcytRoRFLftlp/g45Y/m2b8zE=)
+ * @return 解密验签后的body(sm4解密sm2签名,Json字符串,解密后示例:{xxx:xxx,xxx:xxx})
+ */
+ public static String decryptAndVerify(String keyId, String timestamp, String nonce, String signValue, String body,
+ String decryptKey, String publicKey) throws SdkException {
+
+ //解密后的body
+ String decryptedBody;
+ // 解密
+ try {
+ decryptedBody = SymmetricEncrypt.sm4Decrypt(body, decryptKey);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
+ }
+
+ // 验签
+ if (SdkUtil.notBlank(signValue)) {
+ StringBuilder signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
+ .append(nonce).append("&").append(decryptedBody);
+ try {
+ byte[] bytes = signParams.toString().getBytes(Const.CHARSET);
+ if (!VerifyRespSignature.verifyBySM2(bytes, signValue, publicKey)) {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+ } else {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+
+ return decryptedBody;
+ }
+
+ /**
+ * 对回调请求进行解密验签(V2版本)
+ *
+ * @param keyId 应用ID(请求头中获得,字段名:Keyid,示例:KYONLYFORTEST012345678)
+ * @param timestamp 时间戳(请求头中获得,字段名:Timestamp,示例:20210810185848 其中时间格式为yyyyMMddHHmmss)
+ * @param nonce 随机数(请求头中获得,字段名:Nonce,示例:CGYHt6YaCn7aFm8B76yXTW1Q)
+ * @param pwd 对称秘钥(请求头中获得,字段名:Pwd)
+ * @param signValue 签名值(请求头中获得,字段名:Signature,示例:T3J8o4zY5eGvG4to7xkGfqTZN3Nk4w8NSKG/ADdxhFy+OhhebHUS8t4Wk+w3HSA4K2UXJA5o8AUXm/QAa98FFg==)
+ * @param body 回调请求数据(请求体中获得,参数名:ciphertext,加密内容,示例:{"ciphertext":"xq1hzkLPC4JQC6FVE7nCzroDjWK+3WtYGrYuwpgGsX4="} )
+ * @param priKey 解密秘钥
+ * @param respPubKey 验签公钥
+ * @return 解密验签后的body(sm4解密sm2签名,Json字符串,解密后示例:{xxx:xxx,xxx:xxx})
+ */
+ public static Map decryptAndVerifyV2(String keyId, String timestamp, String nonce, String pwd, String signValue, String body,
+ String priKey, String respPubKey) throws SdkException {
+
+ Map resultMap = new HashMap<>(2);
+ try {
+ // 解密对称秘钥
+ byte[] pwdByte = Signature.decryptBySM2PrivateKey(priKey, Base64.decode(pwd));
+ assert pwdByte != null;
+ pwd = new String(pwdByte, StandardCharsets.UTF_8);
+ resultMap.put("pwd", pwd);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
+ }
+
+ //解密后的body
+ String decryptedBody;
+ // 解密
+ try {
+ decryptedBody = SymmetricEncrypt.sm4Decrypt(body, pwd);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
+ }
+
+ // 验签
+ if (SdkUtil.notBlank(signValue)) {
+ StringBuilder signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
+ .append(nonce).append("&").append(pwd).append("&").append(decryptedBody);
+ try {
+ byte[] bytes = signParams.toString().getBytes(Const.CHARSET);
+ if (!VerifyRespSignature.verifyBySM2(bytes, signValue, respPubKey)) {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+ resultMap.put("body", decryptedBody);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+ } else {
+ throw new SdkException(SdkExType.CALLBACK_REQUEST_VERIFY_ERR);
+ }
+
+ return resultMap;
+ }
+
+ /**
+ * 回调响应签名
+ *
+ * @param keyId 应用ID
+ * @param timestamp 时间戳
+ * @param nonce 随机数
+ * @param body 响应内容
+ * @param privateKey 私钥
+ * @return 签名值
+ */
+ public static String signature(String keyId, String timestamp, String nonce,
+ String body, String privateKey) throws SdkException {
+ StringBuilder signParams;
+ if (SdkUtil.notBlank(keyId) && SdkUtil.notBlank(timestamp) &&
+ SdkUtil.notBlank(nonce) && SdkUtil.notBlank(body)) {
+ signParams = new StringBuilder().append(keyId).append("&").append(timestamp).append("&")
+ .append(nonce).append("&").append(body);
+ } else {
+ throw new SdkException(SdkExType.CALLBACK_RESPONSE_SIGN_ERR);
+ }
+ return Signature.signatureBySM2(signParams.toString(), privateKey, Const.CHARSET);
+ }
+
+ /**
+ * 回调响应加密
+ *
+ * @param body 需要加密的内容
+ * @param encryptKey 加密密钥
+ * @return 密文(Base64 encode)
+ */
+ public static String encrypt(String body, String encryptKey) throws SdkException {
+ String encryptedBody;
+ try {
+ encryptedBody = SymmetricEncrypt.sm4Encrypt(body, encryptKey);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.CALLBACK_BODY_ENCRYPT_ERR);
+ }
+ return encryptedBody;
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Const.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Const.java
new file mode 100644
index 00000000..931cd375
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Const.java
@@ -0,0 +1,67 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import java.text.SimpleDateFormat;
+
+/**
+ * 全局静态常量
+ *
+ * @author zengminghua
+ */
+public final class Const {
+
+ /**
+ * 编码字符集:UTF-8
+ */
+ public static final String CHARSET = "UTF-8";
+
+ /**
+ * 发送请求时head中的认证信息KEY
+ */
+ public static final String POST_HEAD_KEY = "Authorization";
+
+ /**
+ * 时间格式化器
+ */
+ public static final SimpleDateFormat FORMATTER_TIME = new SimpleDateFormat("yyyyMMddHHmmss");
+
+ /**
+ * 签名加密类型:RSA
+ */
+ public static final String SIGN_TYPE = "RSA";
+
+ /**
+ * 加密算法
+ */
+ public static final String SIGN_ALGORITHMS = "SHA256WithRSA";
+
+ /**
+ * EC算法
+ */
+ public static final String ALGORITHM_EC = "EC";
+
+ /**
+ * SM3WithSM2算法
+ */
+ public static final String ALGORITHM_SM3_WITH_SM2 = "SM3WithSM2";
+
+ /**
+ * 下载成功返回消息
+ */
+ public static final String FILE_SUCCESS_RESULT = "{\"code\":\"OPEN25800\",\"msg\":\"下载成功\",\"traceId\":\"OPEN-00-LOCAL-800\"}";
+
+ /**
+ * 文件下载的uri
+ */
+ public static final String FILE_DOWNLOAD_URI = "/api/open/downloadFile";
+
+ /**
+ * 文件上传的uri
+ */
+ public static final String FILE_UPLOAD_URI = "/api/open/uploadFile";
+
+ /**
+ * 异常返回码前缀
+ */
+ public static final String RESP_EXCEPTION_CODE = "\"code\":\"OPEN";
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/FileUtil.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/FileUtil.java
new file mode 100644
index 00000000..d014ed6f
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/FileUtil.java
@@ -0,0 +1,98 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+
+import java.io.*;
+import java.security.MessageDigest;
+
+/**
+ * 文件工具
+ *
+ * @author zengminghua
+ */
+public class FileUtil {
+
+ public static final char[] HEX_CHAR = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+
+ /**
+ * @param filePath 文件路径
+ * @return file bytes
+ * @throws IOException 读取文件错误
+ */
+ public static byte[] readFileByBytes(String filePath) throws IOException {
+ File file = new File(filePath);
+ if (!file.exists()) {
+ throw new FileNotFoundException(filePath);
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream(((int) file.length()));
+ BufferedInputStream in = null;
+ try {
+ in = new BufferedInputStream(new FileInputStream(file));
+ int bufSize = 1024;
+ byte[] buffer = new byte[bufSize];
+ int len;
+ while (-1 != (len = in.read(buffer, 0, bufSize))) {
+ baos.write(buffer, 0, len);
+ }
+ return baos.toByteArray();
+ } finally {
+ try {
+ if (in != null) {
+ in.close();
+ }
+ } catch (IOException e) {
+ // e.printStackTrace();
+ }
+ baos.close();
+ }
+ }
+
+ /**
+ * 计算文件的特征值
+ *
+ * 支持的算法如:"MD2", "MD5", "SHA-1", "SHA-256", "SHA-384", "SHA-512"
+ *
+ * @param filePath 文件路径
+ * @param algorithm 算法
+ * @return hashValue 特征值
+ */
+ public static String fileHash(String filePath, String algorithm) throws Exception {
+ MessageDigest messagedigest = null;
+ try {
+ messagedigest = MessageDigest.getInstance(algorithm);
+
+ InputStream fis = null;
+ try {
+ fis = new FileInputStream(filePath);
+ byte[] buffer = new byte[1024];
+ int numRead = 0;
+ while ((numRead = fis.read(buffer)) > 0) {
+ messagedigest.update(buffer, 0, numRead);
+ }
+ } catch (Exception ex) {
+ // ex.printStackTrace();
+ } finally {
+ if (fis != null) {
+ fis.close();
+ }
+ }
+ } catch (Exception e) {
+ SdkException ex = new SdkException(SdkExType.FILE_ERROR_RESULT);
+ return ex.toString();
+ }
+ return toHexString(messagedigest.digest());
+ }
+
+ public static String toHexString(byte[] b) {
+ StringBuilder sb = new StringBuilder(b.length * 2);
+ for (int i = 0; i < b.length; i++) {
+ sb.append(HEX_CHAR[(b[i] & 0xf0) >>> 4]);
+ sb.append(HEX_CHAR[b[i] & 0x0f]);
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SdkUtil.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SdkUtil.java
new file mode 100644
index 00000000..04ee07df
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SdkUtil.java
@@ -0,0 +1,111 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import java.net.URLEncoder;
+import java.util.Date;
+import java.util.Map;
+
+/**
+ * SDK工具类
+ *
+ * @author zengminghua
+ */
+public class SdkUtil {
+
+ private static final String REQ_SEPARATOR = "/";
+
+ /**
+ * 检查请求URI
+ *
+ * @param reqUri 请求URI
+ * @return 处理后的URI
+ */
+ public static String checkReqUri(String reqUri) {
+ if (reqUri == null || reqUri.length() == 0) {
+ return reqUri;
+ }
+ if (!reqUri.startsWith(REQ_SEPARATOR)) {
+ reqUri = REQ_SEPARATOR + reqUri;
+ }
+ if (reqUri.endsWith(REQ_SEPARATOR)) {
+ reqUri = reqUri.substring(0, reqUri.length() - 1);
+ }
+ return reqUri;
+ }
+
+ /**
+ * 将Map拼接为字符串
+ *
+ * @param paramsMap 参数集
+ * @return 拼接后的参数
+ */
+ public static String jointMap(Map paramsMap) {
+ try {
+ StringBuilder mapBuilder = new StringBuilder();
+ if (paramsMap != null && paramsMap.size() != 0) {
+ for (String key : paramsMap.keySet()) {
+ mapBuilder.append(URLEncoder.encode(key, Const.CHARSET));
+ mapBuilder.append("=");
+ mapBuilder.append(null == paramsMap.get(key) ?
+ null :
+ URLEncoder.encode(paramsMap.get(key), Const.CHARSET));
+ mapBuilder.append("&");
+ }
+ }
+ if (mapBuilder.length() > 1) {
+ mapBuilder.setLength(mapBuilder.length() - 1);
+ }
+ return mapBuilder.toString();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ /**
+ * 获取格式化的时间:yyyyMMddHHmmss
+ *
+ * @return String
+ */
+ public static String getDateTime() {
+ return Const.FORMATTER_TIME.format(new Date());
+ }
+
+ /**
+ * 判断字符串是否为空
+ *
+ * @param str 字符串
+ * @return 判断结果
+ */
+ public static boolean notBlank(String str) {
+ if (null != str && "".equals(str) == false && "".equals(str.trim()) == false) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 判断Map是否为空
+ *
+ * @param map 集合
+ * @return 判断结果
+ */
+ public static boolean notEmpty(Map map) {
+ if (map != null && map.size() != 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 判断字符串是否为JSON格式
+ *
+ * @param str 字符串
+ * @return 判断结果
+ */
+ public static boolean isJSONString(String str) {
+ if (null != str && str.startsWith("{") && str.endsWith("}")) {
+ return true;
+ }
+ return false;
+ }
+
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Signature.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Signature.java
new file mode 100644
index 00000000..5aa75cc7
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/Signature.java
@@ -0,0 +1,264 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.gm.GMNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.engines.SM2Engine;
+import org.bouncycastle.crypto.params.ECDomainParameters;
+import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
+import org.bouncycastle.crypto.params.ECPublicKeyParameters;
+import org.bouncycastle.crypto.params.ParametersWithRandom;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPrivateKeySpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.util.BigIntegers;
+import org.bouncycastle.util.encoders.Hex;
+
+import java.math.BigInteger;
+import java.security.*;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * 签名验签工具类
+ *
+ * @author zengminghua, tangchongcan
+ */
+public class Signature {
+
+ private static final BouncyCastleProvider BC = new BouncyCastleProvider();
+
+ private static final int RS_LEN = 32;
+
+ private static final String ALGORITHM_EC = "EC";
+
+ private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
+ private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
+ x9ECParameters.getG(), x9ECParameters.getN());
+ private static ECDomainParameters ecDomainParameters = new ECDomainParameters(x9ECParameters.getCurve(),
+ x9ECParameters.getG(), x9ECParameters.getN());
+
+ /**
+ * RSA签名
+ *
+ * @param content 待签内容
+ * @param privateKey 开发者私钥
+ * @param charset 字符编码
+ * @return 签名值
+ */
+ public static String signatureByRSA(String content, String privateKey, String charset) {
+ try {
+ PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(Base64.decode(privateKey));
+ KeyFactory keyf = KeyFactory.getInstance(Const.SIGN_TYPE);
+ PrivateKey priKey = keyf.generatePrivate(priPKCS8);
+
+ java.security.Signature signature = java.security.Signature.getInstance(Const.SIGN_ALGORITHMS);
+
+ signature.initSign(priKey);
+ signature.update(content.getBytes(charset));
+
+ byte[] signed = signature.sign();
+ return Base64.encode(signed);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * SM2签名
+ *
+ * @param content 待签内容
+ * @param privateKey 开发者私钥
+ * @param charset 字符编码
+ * @return
+ */
+ public static String signatureBySM2(String content, String privateKey, String charset) {
+ byte[] privateKeyBytes = Base64.decode(privateKey);
+ BCECPrivateKey bcecPrivateKey = getPrivateKeyFromD(BigIntegers.fromUnsignedByteArray(privateKeyBytes));
+ try {
+ byte[] contentBytes = content.getBytes(charset);
+ byte[] signed = rsAsn1ToPlainByteArray(signSm3WithSm2Asn1Rs(contentBytes, bcecPrivateKey));
+ return Base64.encode(signed);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * 使用SM2公钥加密数据
+ *
+ * @param encodedPublicKey Base64编码的公钥
+ * @param contentBytes 待加密内容
+ * @return Base64编码后的密文数据
+ */
+ public static String encryptBySM2PublicKey(String encodedPublicKey, byte[] contentBytes) throws Exception {
+ try {
+ byte[] publicKeyBytes = Base64.decode(encodedPublicKey);
+ if (publicKeyBytes.length != 64 && publicKeyBytes.length != 65) {
+ throw new Exception("err key length");
+ }
+
+ BigInteger x, y;
+ if (publicKeyBytes.length > 64) {
+ x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 1, 32);
+ y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 33, 32);
+ } else {
+ x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 0, 32);
+ y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 32, 32);
+ }
+ BCECPublicKey bcecPublicKey = getPublicKeyFromXY(x, y);
+ byte[] results = changeC1C2C3ToC1C3C2(encryptBySM2PublicKeyOld(contentBytes, bcecPublicKey));
+ return Base64.encode(results);
+ } catch (Exception e) {
+ throw e;
+ }
+ }
+
+ /**
+ * 用SM2私钥解密
+ *
+ * @param encodedPrivateKey Base64编码的私钥
+ * @param contentBytes 待解密内容
+ * @return 明文数据(byte数组)
+ */
+ public static byte[] decryptBySM2PrivateKey(String encodedPrivateKey, byte[] contentBytes) throws Exception {
+ try {
+ byte[] privateKeyBytes = Base64.decode(encodedPrivateKey);
+ BCECPrivateKey bcecPrivateKey = getPrivateKeyFromD(BigIntegers.fromUnsignedByteArray(privateKeyBytes));
+ return decryptBySM2PrivateKeyOld(changeC1C3C2ToC1C2C3(contentBytes), bcecPrivateKey);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * c1||c2||c3
+ *
+ * @param data
+ * @param bcecPrivateKey
+ * @return
+ */
+ public static byte[] decryptBySM2PrivateKeyOld(byte[] data, BCECPrivateKey bcecPrivateKey)
+ throws InvalidCipherTextException {
+ ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(bcecPrivateKey.getD(),
+ ecDomainParameters);
+ SM2Engine sm2Engine = new SM2Engine();
+ sm2Engine.init(false, ecPrivateKeyParameters);
+ return sm2Engine.processBlock(data, 0, data.length);
+ }
+
+ /**
+ * bc加解密使用旧标c1||c3||c2,此方法在解密前调用,将密文转化为c1||c2||c3再去解密
+ *
+ * @param c1c3c2
+ * @return
+ */
+ private static byte[] changeC1C3C2ToC1C2C3(byte[] c1c3c2) {
+ //sm2p256v1的这个固定65
+ final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
+ // 可看GMNamedCurves、ECCurve代码。
+ final int c3Len = 32;
+ byte[] result = new byte[c1c3c2.length];
+ //c1: 0->65
+ System.arraycopy(c1c3c2, 0, result, 0, c1Len);
+ //c2
+ System.arraycopy(c1c3c2, c1Len + c3Len, result, c1Len, c1c3c2.length - c1Len - c3Len);
+ //c3
+ System.arraycopy(c1c3c2, c1Len, result, c1c3c2.length - c3Len, c3Len);
+ return result;
+ }
+
+ /**
+ * c1||c2||c3
+ *
+ * @param data
+ * @param bcecPublicKey
+ * @return
+ */
+ public static byte[] encryptBySM2PublicKeyOld(byte[] data, BCECPublicKey bcecPublicKey)
+ throws InvalidCipherTextException {
+ ECPublicKeyParameters ecPublicKeyParameters = new ECPublicKeyParameters(bcecPublicKey.getQ(),
+ ecDomainParameters);
+ SM2Engine sm2Engine = new SM2Engine();
+ sm2Engine.init(true, new ParametersWithRandom(ecPublicKeyParameters, new SecureRandom()));
+ return sm2Engine.processBlock(data, 0, data.length);
+ }
+
+ /**
+ * bc加解密使用旧标c1||c2||c3,此方法在加密后调用,将结果转化为c1||c3||c2
+ *
+ * @param c1c2c3
+ * @return
+ */
+ private static byte[] changeC1C2C3ToC1C3C2(byte[] c1c2c3) {
+ //sm2p256v1的这个固定65
+ final int c1Len = (x9ECParameters.getCurve().getFieldSize() + 7) / 8 * 2 + 1;
+ // 可看GMNamedCurves、ECCurve代码。
+ final int c3Len = 32;
+ byte[] result = new byte[c1c2c3.length];
+ //c1
+ System.arraycopy(c1c2c3, 0, result, 0, c1Len);
+ //c3
+ System.arraycopy(c1c2c3, c1c2c3.length - c3Len, result, c1Len, c3Len);
+ //c2
+ System.arraycopy(c1c2c3, c1Len, result, c1Len + c3Len, c1c2c3.length - c1Len - c3Len);
+ return result;
+ }
+
+ private static BCECPrivateKey getPrivateKeyFromD(BigInteger d) {
+ ECPrivateKeySpec ecPrivateKeySpec = new ECPrivateKeySpec(d, ecParameterSpec);
+ return new BCECPrivateKey(Const.ALGORITHM_EC, ecPrivateKeySpec, BouncyCastleProvider.CONFIGURATION);
+ }
+
+ private static BCECPublicKey getPublicKeyFromXY(BigInteger x, BigInteger y) {
+ ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
+ ecParameterSpec);
+ return new BCECPublicKey(ALGORITHM_EC, ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
+ }
+
+ private static byte[] bigIntToFixedLengthBytes(BigInteger rOrS) {
+ byte[] rs = rOrS.toByteArray();
+ if (rs.length == RS_LEN) {
+ return rs;
+ } else if (rs.length == RS_LEN + 1 && rs[0] == 0) {
+ return Arrays.copyOfRange(rs, 1, RS_LEN + 1);
+ } else if (rs.length < RS_LEN) {
+ byte[] result = new byte[RS_LEN];
+ Arrays.fill(result, (byte) 0);
+ System.arraycopy(rs, 0, result, RS_LEN - rs.length, rs.length);
+ return result;
+ } else {
+ throw new RuntimeException("err rs: " + Hex.toHexString(rs));
+ }
+ }
+
+ /**
+ * BC的SM3withSM2签名得到的结果的rs是asn1格式的,这个方法转化成直接拼接r||s
+ *
+ * @param rsDer rs in asn1 format
+ * @return sign result in plain byte array
+ */
+ private static byte[] rsAsn1ToPlainByteArray(byte[] rsDer) {
+ ASN1Sequence seq = ASN1Sequence.getInstance(rsDer);
+ byte[] r = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(0)).getValue());
+ byte[] s = bigIntToFixedLengthBytes(ASN1Integer.getInstance(seq.getObjectAt(1)).getValue());
+ byte[] result = new byte[RS_LEN * 2];
+ System.arraycopy(r, 0, result, 0, r.length);
+ System.arraycopy(s, 0, result, RS_LEN, s.length);
+ return result;
+ }
+
+ private static byte[] signSm3WithSm2Asn1Rs(byte[] msg, PrivateKey privateKey)
+ throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
+ java.security.Signature signer = java.security.Signature.getInstance(Const.ALGORITHM_SM3_WITH_SM2, BC);
+ signer.initSign(privateKey, new SecureRandom());
+ signer.update(msg, 0, msg.length);
+ byte[] sig = signer.sign();
+ return sig;
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SymmetricEncrypt.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SymmetricEncrypt.java
new file mode 100644
index 00000000..9898678c
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/SymmetricEncrypt.java
@@ -0,0 +1,289 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+import javax.crypto.Cipher;
+import javax.crypto.KeyGenerator;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.nio.charset.StandardCharsets;
+import java.security.Key;
+import java.security.SecureRandom;
+
+/**
+ * 字段加密工具类
+ *
+ * 支持对称加密算法 AES 的加密、解密,本类涉及的byte编码均为UTF-8
+ *
+ * @author zengminghua
+ */
+public class SymmetricEncrypt {
+
+ private static final BouncyCastleProvider BC = new BouncyCastleProvider();
+
+ /**
+ * 字段加密算法:SM4
+ */
+ private static final String ALGORITHM_SM4 = "SM4";
+
+ /**
+ * 密码器类型,算法/模式/补码方式
+ */
+ private static final String ALGORITHM_SM4_CBC_PKCS5 = "SM4/CBC/PKCS5Padding";
+
+ /**
+ * 字段加密算法:AES
+ */
+ private static final String ALGORITHM_AES = "AES";
+
+ /**
+ * 密码器类型,算法/模式/补码方式
+ */
+ private static final String ALGORITHM_AES_CBC_PKCS5 = "AES/CBC/PKCS5Padding";
+
+ /**
+ * 密钥长度
+ */
+ private static final int SYMMETRIC_KEY_LENGTH = 128;
+
+ /**
+ * 安全随机数算法
+ */
+ private static final String SECURE_RANDOM_ALGORITHMS = "SHA1PRNG";
+
+ /**
+ * 密钥长度
+ */
+ private static final int SM4_KEY_LENGTH = 128;
+
+ /**
+ * AES对称加密
+ *
+ * @param content 待加密内容
+ * @param reqParamEncryptKey 字段加密密钥
+ * @return result 密文(Base64 encode)
+ */
+ public static String aesEncrypt(String content, String reqParamEncryptKey) throws Exception {
+
+ try {
+ // 创建密码器
+ Cipher cipher = Cipher.getInstance(ALGORITHM_AES_CBC_PKCS5);
+
+ // 初始化向量值
+ IvParameterSpec iv = new IvParameterSpec(initIv(ALGORITHM_AES_CBC_PKCS5));
+
+ // 使用密钥初始化,设置为 [加密模式] 的密码器
+ cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(reqParamEncryptKey), iv);
+ byte[] result = cipher.doFinal(content.getBytes(Const.CHARSET));
+
+ // 通过Base64转码返回
+ return Base64.encode(result);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
+ }
+ }
+
+ /**
+ * AES解密操作
+ *
+ * @param content 待解密内容
+ * @param reqParamEncryptKey 字段加密密钥
+ * @return result 明文
+ */
+ public static String aesDecrypt(String content, String reqParamEncryptKey) throws Exception {
+
+ try {
+ // 创建密码器
+ Cipher cipher = Cipher.getInstance(ALGORITHM_AES_CBC_PKCS5);
+
+ // 初始化向量值
+ IvParameterSpec iv = new IvParameterSpec(initIv(ALGORITHM_AES_CBC_PKCS5));
+
+ // 使用密钥初始化,设置为 [解密模式] 的密码器
+ cipher.init(Cipher.DECRYPT_MODE, getSecretKey(reqParamEncryptKey), iv);
+
+ byte[] result = cipher.doFinal(Base64.decode(content));
+ return new String(result, Const.CHARSET);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
+ }
+ }
+
+ /**
+ * 生成密钥对象
+ *
+ * @param symmetricKey 密钥
+ * @return
+ */
+ private static SecretKeySpec getSecretKey(String symmetricKey) throws Exception {
+
+ // 返回生成指定算法密钥生成器的 KeyGenerator 对象
+ KeyGenerator keyGenerator = null;
+
+ try {
+ // 指定为AES的KeyGenerator
+ keyGenerator = KeyGenerator.getInstance(ALGORITHM_AES);
+
+ // 指定随机数
+ SecureRandom random = SecureRandom.getInstance(SECURE_RANDOM_ALGORITHMS);
+ random.setSeed(symmetricKey.getBytes(Const.CHARSET));
+
+ keyGenerator.init(SYMMETRIC_KEY_LENGTH, random);
+ SecretKey secretKey = keyGenerator.generateKey();
+ byte[] enCodeFormat = secretKey.getEncoded();
+
+ // 转换为AES专用密钥
+ SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, ALGORITHM_AES);
+ return secretKeySpec;
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
+ }
+ }
+
+ /**
+ * 生成SM4密钥,返回Base64编码后的密钥值(长度:24)
+ *
+ * @param
+ * @return
+ */
+ public static String generateSM4Key() {
+ byte[] keyBytes = generateSM4KeyBytes();
+ String stringKey = Base64.encode(keyBytes);
+ return stringKey;
+ }
+
+ /**
+ * 生成SM4密钥,返回byte数组
+ *
+ * @param
+ * @return
+ */
+ public static byte[] generateSM4KeyBytes() {
+ KeyGenerator keyGenerator;
+ try {
+ keyGenerator = KeyGenerator.getInstance(ALGORITHM_SM4, BC);
+ } catch (Exception e) {
+ return null;
+ }
+
+ keyGenerator.init(SM4_KEY_LENGTH, new SecureRandom());
+ SecretKey secretKey = keyGenerator.generateKey();
+ return secretKey.getEncoded();
+ }
+
+ /**
+ * 初始向量的方法, 全部为0
+ *
+ *
+ * @param algorithm
+ * @return
+ */
+ private static byte[] initIv(String algorithm) {
+
+ try {
+ Cipher cipher = Cipher.getInstance(algorithm);
+ int blockSize = cipher.getBlockSize();
+ byte[] iv = new byte[blockSize];
+ for (int i = 0; i < blockSize; ++i) {
+ iv[i] = 0;
+ }
+ return iv;
+ } catch (Exception e) {
+
+ int blockSize = 16;
+ byte[] iv = new byte[blockSize];
+ for (int i = 0; i < blockSize; ++i) {
+ iv[i] = 0;
+ }
+ return iv;
+ }
+ }
+
+ /**
+ * 初始向量的方法, 全部为0
+ *
+ * @param algorithm
+ * @return
+ */
+ private static byte[] initBCIv(String algorithm) {
+
+ try {
+ Cipher cipher = Cipher.getInstance(algorithm, BC);
+ int blockSize = cipher.getBlockSize();
+ byte[] iv = new byte[blockSize];
+ for (int i = 0; i < blockSize; ++i) {
+ iv[i] = 0;
+ }
+ return iv;
+ } catch (Exception e) {
+
+ int blockSize = 16;
+ byte[] iv = new byte[blockSize];
+ for (int i = 0; i < blockSize; ++i) {
+ iv[i] = 0;
+ }
+ return iv;
+ }
+ }
+
+ /**
+ * SM4加密
+ *
+ * @param content 待加密内容
+ * @param reqParamEncryptKey 加密密钥
+ * @return 密文(Base64 encode)
+ */
+ public static String sm4Encrypt(String content, String reqParamEncryptKey) throws Exception {
+
+ try {
+ // 创建密码器
+ Cipher cipher = Cipher.getInstance(ALGORITHM_SM4_CBC_PKCS5, BC);
+
+ // 初始化向量值
+ IvParameterSpec iv = new IvParameterSpec(initBCIv(ALGORITHM_SM4_CBC_PKCS5));
+ Key sm4Key = new SecretKeySpec(Base64.decode(reqParamEncryptKey), ALGORITHM_SM4);
+ // 使用密钥初始化,设置为 [加密模式] 的密码器
+ cipher.init(Cipher.ENCRYPT_MODE, sm4Key, iv);
+
+ // 加密
+ byte[] result = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));
+
+ // 通过Base64转码返回
+ return Base64.encode(result);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
+ }
+ }
+
+ /**
+ * SM4解密操作
+ *
+ * @param content 待解密内容
+ * @param reqParamEncryptKey 解密密钥
+ * @return 明文
+ */
+ public static String sm4Decrypt(String content, String reqParamEncryptKey) throws Exception {
+
+ try {
+ // 创建密码器
+ Cipher cipher = Cipher.getInstance(ALGORITHM_SM4_CBC_PKCS5, BC);
+
+ // 初始化向量值
+ IvParameterSpec iv = new IvParameterSpec(initBCIv(ALGORITHM_SM4_CBC_PKCS5));
+ Key sm4Key = new SecretKeySpec(Base64.decode(reqParamEncryptKey), ALGORITHM_SM4);
+ // 使用密钥初始化,设置为 [解密模式] 的密码器
+ cipher.init(Cipher.DECRYPT_MODE, sm4Key, iv);
+
+ // 解密
+ byte[] result = cipher.doFinal(Base64.decode(content));
+
+ // 返回明文
+ return new String(result, StandardCharsets.UTF_8);
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.REQ_PARAM_ENCRYPT_ERR);
+ }
+ }
+}
diff --git a/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/VerifyRespSignature.java b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/VerifyRespSignature.java
new file mode 100644
index 00000000..92a7f1e9
--- /dev/null
+++ b/server/dfp-open-sdk/src/main/java/com/cib/fintech/dfp/open/sdk/util/VerifyRespSignature.java
@@ -0,0 +1,189 @@
+package com.cib.fintech.dfp.open.sdk.util;
+
+import com.cib.fintech.dfp.open.sdk.config.KeyConfigure;
+import com.cib.fintech.dfp.open.sdk.enums.RespSignAlgorithmEnum;
+import com.cib.fintech.dfp.open.sdk.exception.SdkExType;
+import com.cib.fintech.dfp.open.sdk.exception.SdkException;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.gm.GMNamedCurves;
+import org.bouncycastle.asn1.x9.X9ECParameters;
+import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.bouncycastle.jce.spec.ECParameterSpec;
+import org.bouncycastle.jce.spec.ECPublicKeySpec;
+import org.bouncycastle.util.BigIntegers;
+
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyFactory;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.Arrays;
+
+/**
+ * 响应报文验签工具类
+ *
+ * @author zengminghua
+ */
+public class VerifyRespSignature {
+
+ private static final BouncyCastleProvider BC = new BouncyCastleProvider();
+
+ private static final int RS_LEN = 32;
+
+ private static X9ECParameters x9ECParameters = GMNamedCurves.getByName("sm2p256v1");
+ private static ECParameterSpec ecParameterSpec = new ECParameterSpec(x9ECParameters.getCurve(),
+ x9ECParameters.getG(), x9ECParameters.getN());
+
+ /**
+ * 响应报文验签
+ *
+ * @param timestamp 响应报文头部的时间戳
+ * @param nonce 响应报文头部的随机值
+ * @param responseSignValue 响应报文头部的签名值
+ * @param retn 响应报文
+ * @throws SdkException
+ */
+ public static void verify(String timestamp, String nonce, String responseSignValue, Object retn,
+ KeyConfigure keyConfigure) throws SdkException {
+
+ if (SdkUtil.notBlank(timestamp) && SdkUtil.notBlank(responseSignValue)) {
+ try {
+ String respPubKey = keyConfigure.getRespPubKey();
+ String verifyAlgorithm = keyConfigure.getRespSignAlgorithm().getLabel();
+ byte[] bodyContentBytes;
+ if (retn instanceof String) {
+ String content = (String) retn;
+ bodyContentBytes = content.getBytes(Const.CHARSET);
+ } else {
+ bodyContentBytes = (byte[]) retn;
+ }
+
+ // 待验签bytes内容为时间戳bytes+报文bodyBytes
+ byte[] timestampBytes = timestamp.getBytes(Const.CHARSET);
+ byte[] nonceBytes = nonce.getBytes(Const.CHARSET);
+ byte[] contentBytes = new byte[timestampBytes.length + nonceBytes.length + bodyContentBytes.length];
+ System.arraycopy(timestampBytes, 0, contentBytes, 0, timestampBytes.length);
+ System.arraycopy(nonceBytes, 0, contentBytes, timestampBytes.length, nonceBytes.length);
+ System.arraycopy(bodyContentBytes, 0, contentBytes, timestampBytes.length + nonceBytes.length,
+ bodyContentBytes.length);
+
+ boolean flag;
+ if (RespSignAlgorithmEnum.SM3WITHSM2.equals(keyConfigure.getRespSignAlgorithm())) {
+ flag = verifyBySM2(contentBytes, responseSignValue, respPubKey);
+ } else {
+ flag = verifyByRSA(contentBytes, responseSignValue, respPubKey, verifyAlgorithm);
+ }
+
+ if (!flag) {
+ throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
+ }
+ } catch (Exception e) {
+ throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
+ }
+ } else {
+ throw new SdkException(SdkExType.RESPONSE_SIGN_ERR);
+ }
+ }
+
+ /**
+ * 验签,支持SHA1WithRSA、SHA256WithRSA算法
+ *
+ * @param contentBytes 待验签名的内容
+ * @param sign 签名值
+ * @param publicKey 公钥
+ * @param verifyAlgorithm 验签算法
+ * @return 验签结果(true / false)
+ */
+ private static boolean verifyByRSA(byte[] contentBytes, String sign, String publicKey, String verifyAlgorithm) {
+ try {
+ byte[] b = Base64.decode(publicKey);
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(b);
+ KeyFactory factoty = KeyFactory.getInstance(Const.SIGN_TYPE);
+ PublicKey pubKey = factoty.generatePublic(keySpec);
+ java.security.Signature signature = java.security.Signature.getInstance(verifyAlgorithm);
+ signature.initVerify(pubKey);
+ signature.update(contentBytes);
+
+ return signature.verify(Base64.decode(sign));
+ } catch (Exception e) {
+ // e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * SM3WithSM2验签
+ *
+ * @param contentBytes 待验签名的内容
+ * @param sign 签名值
+ * @param publicKey 公钥
+ * @return
+ */
+ public static boolean verifyBySM2(byte[] contentBytes, String sign, String publicKey) {
+ byte[] publicKeyBytes = Base64.decode(publicKey);
+ if (publicKeyBytes.length != 64 && publicKeyBytes.length != 65) {
+ return false;
+ }
+
+ BigInteger x, y;
+ if (publicKeyBytes.length > 64) {
+ x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 1, 32);
+ y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 33, 32);
+ } else {
+ x = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 0, 32);
+ y = BigIntegers.fromUnsignedByteArray(publicKeyBytes, 32, 32);
+ }
+ BCECPublicKey bcecPublicKey = getPublicKeyFromXY(x, y);
+ byte[] rs = Base64.decode(sign);
+ return verifySm3WithSm2Asn1Rs(contentBytes, rsPlainByteArrayToAsn1(rs), bcecPublicKey);
+ }
+
+ private static BCECPublicKey getPublicKeyFromXY(BigInteger x, BigInteger y) {
+ ECPublicKeySpec ecPublicKeySpec = new ECPublicKeySpec(x9ECParameters.getCurve().createPoint(x, y),
+ ecParameterSpec);
+ return new BCECPublicKey(Const.ALGORITHM_EC, ecPublicKeySpec, BouncyCastleProvider.CONFIGURATION);
+ }
+
+ /**
+ * BC的SM3withSM2验签需要的rs是asn1格式的,这个方法将直接拼接r||s的字节数组转化成asn1格式
+ *
+ * @param sign in plain byte array
+ * @return rs result in asn1 format
+ */
+ public static byte[] rsPlainByteArrayToAsn1(byte[] sign) {
+ if (sign.length != RS_LEN * 2) {
+ throw new RuntimeException("err rs. ");
+ }
+ BigInteger r = new BigInteger(1, Arrays.copyOfRange(sign, 0, RS_LEN));
+ BigInteger s = new BigInteger(1, Arrays.copyOfRange(sign, RS_LEN, RS_LEN * 2));
+ ASN1EncodableVector v = new ASN1EncodableVector();
+ v.add(new ASN1Integer(r));
+ v.add(new ASN1Integer(s));
+ try {
+ return new DERSequence(v).getEncoded("DER");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * @param msg
+ * @param rs in asn1 format
+ * @param publicKey
+ * @return
+ */
+ private static boolean verifySm3WithSm2Asn1Rs(byte[] msg, byte[] rs, PublicKey publicKey) {
+ try {
+ Signature signature = Signature.getInstance(Const.ALGORITHM_SM3_WITH_SM2, BC);
+ signature.initVerify(publicKey);
+ signature.update(msg, 0, msg.length);
+ return signature.verify(rs);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+}
diff --git a/server/like-common/pom.xml b/server/like-common/pom.xml
index 97e6a934..2ed6d68a 100644
--- a/server/like-common/pom.xml
+++ b/server/like-common/pom.xml
@@ -225,6 +225,16 @@
weixin-java-pay
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
com.google.zxing
diff --git a/server/like-front/pom.xml b/server/like-front/pom.xml
index 66e200c6..92810d20 100644
--- a/server/like-front/pom.xml
+++ b/server/like-front/pom.xml
@@ -23,6 +23,12 @@
like-common
+
+
+ org.mdd
+ dfp-open-sdk
+
+
cn.dev33
diff --git a/server/pom.xml b/server/pom.xml
index 03f0e37f..1ae1bae3 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -13,6 +13,7 @@
like-admin
like-front
like-common
+ dfp-open-sdk
mozhe-enrollment
like-generator
@@ -56,6 +57,9 @@
4.4.0
3.5.1
+ 1.60
+
+ 1.2.83
@@ -263,12 +267,37 @@
${zxing.version}
+
+ org.bouncycastle
+ bcprov-jdk15on
+ ${jdk15on.version}
+
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+ ${jdk15on.version}
+
+
org.mdd
like-common
${like.version}
+
+
+
+ org.mdd
+ dfp-open-sdk
+ ${like.version}
+
+
+
+ com.alibaba
+ fastjson
+ ${fastjson-legacy.version}
+