对接环境、验签、加解密
## 对接环境
开发环境:https://pay-dev.lx-rhino.com
生产环境:https://api.lx-rhino.com
>d ◆ JAVA DEMO仓库地址:
**https://code.aliyun.com/lanxi-server/open-api-tool-demo.git**
### 接口对接流程
![api对接流程.png](https://cos.easydoc.net/74722269/files/kw0qol1h.png)
## 1. 请求参数说明
### 1.1 参数加密
>d RSA加密说明
◆ 加密长度为512
◆ 需要分段加密防止数据太长导致加密失败
◆ 获取到加密结果后通过base64转成字符串即为加密后的数据
◆ 如参数为空字符串无需加密
#### 加密代码示例(java)
```java
public static void main(String[] args) {
// 明文
String paramsJson= "{\"key1\":\"value1\",\"key2\":\"value2\"}";
// publicKey:公钥,开户后将发送至邮箱
String encryptResult= encryptByPublicKey(paramsJson, publicKey);
}
/**
* 使用公钥加密
*
* @param content 待加密内容
* @param publicKeyBase64 公钥 base64 编码(发放平台给的publicKey)
* @return 经过 base64 编码后的字符串
*/
public static String encryptByPublicKey(String content, String publicKeyBase64) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyBase64);
return encrypt(content, publicKey);
}
public static String encrypt(String ciphertext, Key key) {
try {
// 用公钥加密
byte[] srcBytes = ciphertext.getBytes();
// Cipher负责完成加密或解密工作,基于RSA
Cipher cipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
cipher.init(Cipher.ENCRYPT_MODE, key);
//分段加密
byte[] resultBytes = encryptCipherDoFinal(cipher, srcBytes);
//base64编码
String base64Str = Base64Utils.encodeToString(resultBytes);
return base64Str;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static byte[] encryptCipherDoFinal(Cipher cipher, byte[] srcBytes)
throws IllegalBlockSizeException, BadPaddingException, IOException {
int inputLen = srcBytes.length;
int offLen = 0;//偏移量
int i = 0;
ByteArrayOutputStream bops = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 53) {
cache = cipher.doFinal(srcBytes, offLen, 53);
} else {
cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen);
}
bops.write(cache);
i++;
offLen = 53 * i;
}
bops.close();
byte[] encryptedData = bops.toByteArray();
return encryptedData;
}
```
### 1.2 签名算法
#### 第一步:
将加密结果跟公共字段拼接成新的字符串
```java
String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey;
```
>d 备注:该处特别说明 &SecretKey 后面没有 ==等于==号
#### 第二步:
用MD5对新字符串加密即为签名
#### 代码示例(java)
```java
public static void main(String[] args) {
//约定拼接参数
//encryptResult:加密后的入参
//timestamp:时间戳,与http请求时入参保持一致
//appKey:平台分配的appKey
//secretKey:平台分配的秘钥
String localString = encryptResult + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey;
//md5加密
String sigin = Md5Util.getMd5(localString);
}
private final static String[] CHARS = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};
public static String getMd5(String src) {
MessageDigest md5 = null;
try {
// 参数代表的是算法名称
md5 = MessageDigest.getInstance("md5");
byte[] result = md5.digest(src.getBytes());
StringBuilder sb = new StringBuilder(32);
// 将结果转为16进制字符 0~9 A~F
for (int i = 0; i < result.length; i++) {
// 一个字节对应两个字符
byte x = result[i];
// 取得高位
int h = 0x0f & (x >>> 4);
// 取得低位
int l = 0x0f & x;
sb.append(CHARS[h]).append(CHARS[l]);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
```
## 2、响应报文说明
>d RSA解密说明
◆ 需要分段解密密防止数据太长导致解密失败
◆ 获取到加密结果后直接转成字符串即为解密结果
|参数名称|参数类型|参数含义|是否必填|参数备注|
|-|-|-|-|-|
|success|Boolean|请求成功与失败|必填|false表示业务异常|
|status|Integer|状态码|必填|系统响应编码|
|errorCode|String|错误编码|否|请求失败返回(预留字段)|
|errorMessage|String|错误原因|否|请求失败返回|
|data|String|业务数据(密文)|否|请求成果返回,响应对应的业务数据,此数据是密文,需要解密|
获取接口返回结果中的data字段,该值即为需要解密的数据
```
{
"success": true,
"status": 0,
"errorCode": "",
"errorMessage": "",
"data": "jaG/AODOH76rqIzaAPLLZA2Fp4k6ujXHh3jewVpCSNJFIL4JByl6byDu1oe4K6iiaym8yLNMhlisxt1IlDRu6A=="
}
```
#### 解密代码示例(java)
```java
public static void main(String[] args) {
// data:密文
String data = "jaG/AODOH76rqIzaAPLLZA2Fp4k6ujXHh3jewVpCSNJFIL4JByl6byDu1oe4K6iiaym8yLNMhlisxt1IlDRu6A==";
// publicKey:公钥,开户后将发送至邮箱
String decryptResult= decryptByPublicKey(data, publicKey);
}
/**
* 使用公钥解密
*
* @param contentBase64 待解密内容,base64 编码
* @param publicKeyBase64 私钥 base64 编码
* @return
*/
public static String decryptByPublicKey(String contentBase64, String publicKeyBase64) throws Exception {
PublicKey publicKey = getPublicKey(publicKeyBase64);
return decrypt(contentBase64, publicKey);
}
public static String decrypt(String contentBase64, Key key) {
try {
// 用私钥解密
byte[] bytes = Base64Utils.decodeFromString(contentBase64);
// Cipher负责完成加密或解密工作,基于RSA
Cipher deCipher = Cipher.getInstance("RSA");
// 根据公钥,对Cipher对象进行初始化
deCipher.init(Cipher.DECRYPT_MODE, key);
// 分段解密
byte[] decBytes = decryptCipherDoFinal(deCipher, bytes);
// 结果转成字符串
String decrytStr = new String(decBytes);
return decrytStr;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static byte[] decryptCipherDoFinal(Cipher cipher, byte[] srcBytes)
throws IllegalBlockSizeException, BadPaddingException, IOException {
int inputLen = srcBytes.length;
int offLen = 0;
int i = 0;
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
while (inputLen - offLen > 0) {
byte[] cache;
if (inputLen - offLen > 64) {
cache = cipher.doFinal(srcBytes, offLen, 64);
} else {
cache = cipher.doFinal(srcBytes, offLen, inputLen - offLen);
}
byteArrayOutputStream.write(cache);
i++;
offLen = 64 * i;
}
byteArrayOutputStream.close();
byte[] data = byteArrayOutputStream.toByteArray();
return data;
}
```
## 3、接口请求
>d http请求说明
◆ 以下公共参数放在header中进行传递
#### 请求头部信息
|参数名称|参数含义|是否必填|参数备注|
|-|-|-|-|
|AppKey|由发放平台提供的|必填|商户签约后发放提供的唯一识别号|
|Timestamp|时间戳|必填|请求时间戳精确到毫秒。需要将请求机器时间调整为北京时间,请求有效时间为两分钟|
|Version|版本号|必填|如果无特别说明则填写1.0即可|
|Sign|签名|必填|签名,具体来源请看==签名算法==|
#### 请求代码示例(java)
```java
public static void main(String[] args) throws Exception {
JSONObject jsonObject = new JSONObject();
jsonObject.put("key1", "value1");
jsonObject.put("key2", "value2");
//http请求
doPost("/open/api/xxx/xxx/xxx", jsonObject.toJSONString());
}
//平台分配的appKey
private static final String appKey = "xxx";
//凭条分配的秘钥
private static final String secretKey = "xxx";
//平台分配的公钥
private static final String publicKey = "xxx";
public static void doPost(String url, String paramsJson) throws Exception {
System.out.println("请求参数:" + paramsJson);
//加密
String encryptParam = RSAHelper.encryptByPublicKey(paramsJson, publicKey);
System.out.println("-->加密:" + encryptParam);
String timestamp = String.valueOf(System.currentTimeMillis());
//接口请求
BaseResponse baseResponse = HttpUtil.post(url, encryptParam, appKey, timestamp, getSign(encryptParam, timestamp));
if (baseResponse.getStatus() == 0) {
if (baseResponse.getData() == null) {
return;
}
String data = baseResponse.getData();
//解密
if (StringUtils.isNotBlank(data)) {
String dec = RSAHelper.decryptByPublicKey(data, publicKey);
System.out.println("-->解密:" + dec);
}
}
}
//获取签名
private static String getSign(String encryptParam, String timestamp) {
return Md5Util.getMd5(encryptParam + "&Timestamp=" + timestamp + "&AppKey=" + appKey + "&Version=1.0&SecretKey" + secretKey);
}
/**
* http请求
* @param url 接口地址
* @param paramsJson 请求参数json格式的字符串
* @param appKey 平台分配的appKey
* @param timestamp 时间戳
* @param sign 签名
*/
public static BaseResponse post(String url, String paramsJson, String appKey, String timestamp, String sign) throws Exception {
RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), paramsJson);
Request request = new Request.Builder()
.addHeader("AppKey", appKey)
.addHeader("Timestamp", timestamp)
.addHeader("Version", "1.0")
.addHeader("Sign", sign)
.url(url)
.post(body)
.build();
Call call = getOkHttpClient().newCall(request);
Response response = call.execute();
String res = response.body().string();
System.out.println("响应报文:" + res);
return JSONObject.parseObject(res, BaseResponse.class);
}
```
## 4、回调说明
>d 1、该接口需要第三方自行编写提供接口到发放平台 接口编写示例如下。
2、回调必传callbackType用于区分回调业务。
3、贵司写完后提供到接口url给发放平台即可。回调成功需返回success,否则视为失败,每次失败会进行回调重试,重试9次后将不再回调
#### 请求方式:POST
|参数名称|参数类型|参数含义|是否必填|参数备注|
|-|-|-|-|-|
|...|...|...|...|...|
```java
@PostMapping("signCallBack")
@ResponseBody
public String signCallBack(HttpServletRequest request, @RequestBody String data){
String publicKey = "该公钥由发放平台提供发放到贵司邮箱";
//解密
String decode = RSAHelper.decryptByPublicKey(data, publicKey);
/*贵公司处理业务逻辑*/
return "success";
}
```
#### postman中模拟接口是否正常接收数据如下操作
![微信截图_20191111122018.png](https://cos.easydoc.net/44383407/files/k2tx5nl4.png)