请求示例代码-JAVA版
<pre><code>package com.yzm.center.open.gateway;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ByteUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import java.io.Serializable;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.function.BiFunction;
@Slf4j
public class SignTest {
private static String url = &quot;https://open-beta.56yzm.com:8443&quot;;
private static String clientId = &quot;分配的clientId&quot;;
private static String clientSecret = &quot;管理员分配的clientSecret&quot;;
private static String sign_secret_key = &quot;管理员分配的sign_secret_key&quot;;
private static String sign_public_key = &quot;管理员分配的sign_public_key&quot;;
private static String ivs_public_key = &quot;管理员分配的ivs_public_key&quot;;
private static String userCode = &quot;管理员分配的user-code&quot;;
private static List&lt;String&gt; fields = Arrays.asList(&quot;idCardNum&quot;, &quot;phone&quot;, &quot;password&quot;, &quot;idCardNo&quot;, &quot;idCard&quot;, &quot;qualificationCardNo&quot;, &quot;bankIdCardNo&quot;, &quot;legalPersonIdCard&quot;, &quot;bankPhone&quot;, &quot;adminPhone&quot;, &quot;financeContactTel&quot;, &quot;businessContactTel&quot;, &quot;userPhone&quot;, &quot;payeePhone&quot;, &quot;driverPhone&quot;, &quot;oldPayPwd&quot;, &quot;newPayPwd&quot;, &quot;confirmPayPwd&quot;);
public static void main(String[] args) {
String apiUrl = String.format(&quot;%s%s&quot;, url, &quot;/api-server/openapi/vehicle/v2/createVehicle&quot;);
String reqJsonStr = &quot;{\&quot;carInfoDto\&quot;:{\&quot;carDrivingCopyAddImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;carDrivingCopyImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;carDrivingFrontImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;carLength\&quot;:11,\&quot;carTypesOf\&quot;:17,\&quot;plateColor\&quot;:\&quot;YELLOW\&quot;,\&quot;plateNo\&quot;:\&quot;晋K62255\&quot;,\&quot;roadTransportPermImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;trailerCarDrivingCopyImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;trailerCarDrivingFrontImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;trailerNo\&quot;:\&quot;晋K6U92挂\&quot;,\&quot;useCharacter\&quot;:\&quot;货运\&quot;},\&quot;driverInfoDto\&quot;:{\&quot;driverName\&quot;:\&quot;孟庆祯\&quot;,\&quot;drivingCopyImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;drivingImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;idCardBackImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;idCardFrontImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;,\&quot;idCardNo\&quot;:\&quot;429006199005131414\&quot;,\&quot;phone\&quot;:\&quot;15113151415\&quot;,\&quot;qualificationCardImg\&quot;:\&quot;https://file.rlsk.link/12961571/fb14799fda39444d978972d9b85204f3.jpeg\&quot;},\&quot;yzmUserCode\&quot;:\&quot;YZM_9dO0fhTtOt\&quot;}&quot;;
Security.addProvider(new BouncyCastleProvider());
//把请求的原始参数,进行加密(有需要加密的字段,才进行加密)
String reqJson = startHandleJsonFields(reqJsonStr, ivs_public_key, fields, 1);
String token = getYzmToken(clientId, clientSecret);
YzmSignDto yzmSign = getSign(sign_secret_key, reqJson);
YzmResultBasicDto yzmResultBasicDto = postToYzm(YzmResultBasicDto.class, apiUrl, token, yzmSign, reqJson, userCode);
System.out.println(JSONObject.toJSONString(yzmResultBasicDto));
}
/**
* 获取 token
*
* @param clientId
* @param clientSecret
* @return
*/
public static String getYzmToken(String clientId, String clientSecret) {
HttpResponse httpResponse = HttpUtil
.createPost(String.format(&quot;%s%s&quot;, url, &quot;/open-oauth/oauth/token&quot;))
.contentType(&quot;application/x-www-form-urlencoded&quot;)
.form(&quot;grant_type&quot;, &quot;client_credentials&quot;)
.form(&quot;client_id&quot;, clientId)
.form(&quot;client_secret&quot;, clientSecret)
.form(&quot;scope&quot;, &quot;read&quot;)
.execute();
if (httpResponse.getStatus() != 200) {
log.error(&quot;运之盟-开放平台登录异常:{}&quot;, httpResponse.getStatus());
}
String body = httpResponse.body();
YzmTokenDto yzmTokenDto = JSONObject.parseObject(body, YzmTokenDto.class);
return yzmTokenDto.getAccessToken();
}
/**
* 生成签名
*
* @param signSecretKey 加密SecretKey
* @param jsonStr 待签名参数 json
* @return
*/
private static YzmSignDto getSign(String signSecretKey, String jsonStr) {
try {
long timeMillis = System.currentTimeMillis();
long nonce = generateNonce();
String sortJsonByKey = sortJsonByKey(jsonStr);
String signString = sign(signSecretKey, sortJsonByKey.getBytes(), nonce, timeMillis);
YzmSignDto yzmSignDto = new YzmSignDto();
yzmSignDto.setSign(signString);
yzmSignDto.setTimeMillis(timeMillis);
yzmSignDto.setNonce(nonce);
return yzmSignDto;
} catch (Exception e) {
log.error(&quot;获取签名异常:&quot; + e);
return null;
}
}
/**
* 验证签名
*
* @param pkStr 公钥字符串
* @param data 数据(json数据通过 sortJsonByKey 方法排序的 json 数据)
* @param sign sign 签名
* @param nonce 现时标志
* @param timestamp 时间戳
* @return boolean
* @throws NoSuchAlgorithmException
* @throws InvalidKeyException
* @throws SignatureException
*/
public boolean verifySignature(String pkStr, byte[] data, String sign, long nonce, long timestamp) {
PublicKey publicKey = publicKeyFromString(pkStr);
Signature signature = null;
try {
signature = Signature.getInstance(&quot;SHA256withECDSA&quot;);
signature.initVerify(publicKey);
if (!isTimestampValid(timestamp)) {
log.error(&quot;无效签名 , timestamp&quot;);
return false;
}
// 将原始数据、时间戳和nonce结合在一起进行验签
signature.update(data);
signature.update(ByteUtil.longToBytes(timestamp));
signature.update(ByteUtil.longToBytes(nonce));
return signature.verify(Base64.getDecoder().decode(sign));
} catch (NoSuchAlgorithmException e) {
log.error(&quot;请确保所请求的签名算法为SHA256withECDSA算法,并且必要的加密提供者可用&quot;);
return false;
} catch (InvalidKeyException e) {
log.error(&quot;提供的密钥无效。请使用正确的EC密钥&quot;);
return false;
} catch (SignatureException e) {
log.error(&quot;签名验证异常,请检查密钥是否正确, 请求体是否完整, 签名算法是否一致&quot;);
return false;
}
}
/**
* 时间戳是否有效
* 和服务器时间作对比, 10分钟以内则有效
*
* @param timestamp 时间戳
* @return boolean
*/
private boolean isTimestampValid(long timestamp) {
long currentTime = System.currentTimeMillis();
long difference = Math.abs(currentTime - timestamp);
return difference &lt;= 10 * 60 * 1000; // 10分钟
}
/**
* 递归 对 json 字符串 进行 加解密
*
* @param jsonStr 待加解密 字符串
* @param key 公钥
* @param fields 需要加密的字段名称
* @param type 加解密类型 1:加密 2:解密
* @return
*/
public static String startHandleJsonFields(String jsonStr, String key, List&lt;String&gt; fields, int type) {
if (CollUtil.isEmpty(fields)) {
return jsonStr;
}
Object json = JSON.parse(jsonStr);
BiFunction function = null;
switch (type) {
case 1:
function = (key1, json1) -&gt; encrypt((String) key1, (String) json1);
break;
case 2:
function = (key1, json1) -&gt; decrypt((String) key1, (String) json1);
break;
default:
break;
}
Object processedJson = handleJsonFieldsDeep(json, fields, key, function);
return JSON.toJSONString(processedJson);
}
/**
* 递归深度解密json字段
*
* @param json json
* @param fields 字段
* @param key 他私钥
* @return {@link Object}
*/
private static Object handleJsonFieldsDeep(Object json, List&lt;String&gt; fields, String key, BiFunction&lt;String, String, String&gt; function) {
if (json instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) json;
Map&lt;String, Object&gt; processedMap = new LinkedHashMap&lt;&gt;();
for (String jsonKey : jsonObject.keySet()) {
Object value = jsonObject.get(jsonKey);
if (fields.contains(jsonKey) &amp;&amp; value instanceof String) {
// 加/解密
value = new String(function.apply(key, (String) value));
} else if (value instanceof JSONObject || value instanceof JSONArray) {
value = handleJsonFieldsDeep(value, fields, key, function);
}
processedMap.put(jsonKey, value);
}
return processedMap;
} else if (json instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) json;
for (int i = 0; i &lt; jsonArray.size(); i++) {
jsonArray.set(i, handleJsonFieldsDeep(jsonArray.get(i), fields, key, function));
}
return jsonArray;
}
return json;
}
/**
* 使用私钥解密数据。
*/
public static String decrypt(String skStr, String encryptedData) {
try {
PrivateKey privateKey = privateKeyFromString(skStr);
Cipher cipher = Cipher.getInstance(&quot;ECIES&quot;);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return new String(cipher.doFinal(Base64.getDecoder().decode(encryptedData)), &quot;utf-8&quot;);
} catch (Exception e) {
log.error(&quot;解密错误&quot;);
return null;
}
}
/**
* 敏感字段-请求字符串加密
*
* @param pkStr 公钥
* @param str 待加密字符串
* @return
*/
public static String encrypt(String pkStr, String str) {
try {
byte[] plaintext = str.getBytes();
PublicKey publicKey = publicKeyFromString(pkStr);
Cipher cipher = Cipher.getInstance(&quot;ECIES&quot;);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(plaintext));
} catch (Exception e) {
log.error(&quot;字段加密报错&quot;);
return null;
}
}
/**
* 来自字符串公钥
*
* @param keyStr 关键str
* @return {@link PublicKey}
*/
public static PublicKey publicKeyFromString(String keyStr) {
try {
byte[] decodedKey = Base64.getDecoder().decode(keyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(decodedKey);
KeyFactory keyFactory = KeyFactory.getInstance(&quot;EC&quot;);
return keyFactory.generatePublic(spec);
} catch (Exception e) {
log.error(&quot;字段报错&quot;);
return null;
}
}
/**
* @param clazz 待转换类型
* @param apiUrl 请求地址
* @param token token
* @param yzmSign 当前毫秒时间戳
* @param json body
* @param &lt;T&gt; 返回类型
* @return
*/
private static &lt;T&gt; T postToYzm(Class&lt;T&gt; clazz, String apiUrl, String token, YzmSignDto yzmSign, String json, String userCode) {
long startTime = System.currentTimeMillis();
log.info(&quot;运之盟开放平台-请求参数:{}&quot;, json);
HttpResponse authorization = HttpUtil.createPost(apiUrl)
.contentType(&quot;application/json&quot;)
.header(&quot;Authorization&quot;, &quot;Bearer &quot; + token)
.header(&quot;X-Timestamp&quot;, String.valueOf(yzmSign.getTimeMillis()))
.header(&quot;X-Nonce&quot;, String.valueOf(yzmSign.getNonce()))
.header(&quot;X-Signature&quot;, yzmSign.getSign())
.header(&quot;user-code&quot;, userCode)
.body(json)
.execute();
//验证结果
if (authorization.getStatus() != 200) {
log.error(&quot;请求异常&quot;);
}
long endTime = System.currentTimeMillis();
log.info(&quot;运之盟开放平台-耗时:{},返回参数:{}&quot;, endTime - startTime, authorization.body());
return JSONObject.parseObject(authorization.body(), clazz);
}
private static long generateNonce() {
long minimum = 100000000000000000L; // 10^17
long maximum = 999999999999999999L; // Just below 10^18
return minimum + ((long) (new SecureRandom().nextDouble() * (maximum - minimum)));
}
/**
* 签名
*
* @param skStr 私钥字符串
* @param data 数据byte数组
* @param nonce 现时标志
* @param timestamp 时间戳
* @return {@link String} base64 的签名
*/
public static String sign(String skStr, byte[] data, long nonce, long timestamp) throws Exception {
try {
// 私钥 base64 String 转真实 私钥
PrivateKey privateKey = privateKeyFromString(skStr);
Signature signature = Signature.getInstance(&quot;SHA256withECDSA&quot;);
signature.initSign(privateKey);
// 将原始数据、时间戳和nonce结合在一起进行签名
signature.update(data);
signature.update(ByteUtil.longToBytes(timestamp));
signature.update(ByteUtil.longToBytes(nonce));
// 使用Base64 将真实签名转换位字符串
return Base64.getEncoder().encodeToString(signature.sign());
} catch (Exception e) {
log.error(&quot;签名错误&quot;);
return null;
}
}
/**
* 字符串转私钥
*
* @param keyStr 关键str
* @return {@link PrivateKey}
* @throws Exception 异常
*/
public static PrivateKey privateKeyFromString(String keyStr) throws Exception {
byte[] decodedKey = Base64.getDecoder().decode(keyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(decodedKey);
KeyFactory keyFactory = KeyFactory.getInstance(&quot;EC&quot;);
return keyFactory.generatePrivate(spec);
}
/**
* 排序json参数
*
* @param jsonStr json str
* @return {@link String}
*/
public static String sortJsonByKey(String jsonStr) {
Object json = JSON.parse(jsonStr);
Object sortedJson = sortObjectByKey(json);
return JSON.toJSONString(sortedJson);
}
/**
* 递归 排序json对象 参数, 私有方法, 不对外开放
*
* @param json json
* @return {@link Object}
*/
private static Object sortObjectByKey(Object json) {
if (json instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) json;
Map&lt;String, Object&gt; sortedMap = new LinkedHashMap&lt;&gt;();
jsonObject.keySet().stream()
.sorted() // 默认的字符串排序,即字典序 (a-z)
.forEach(key -&gt; sortedMap.put(key, sortObjectByKey(jsonObject.get(key))));
return sortedMap;
} else if (json instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) json;
for (int i = 0; i &lt; jsonArray.size(); i++) {
jsonArray.set(i, sortObjectByKey(jsonArray.get(i)));
}
return jsonArray;
}
return json;
}
@Data
public static class YzmSignDto {
public String sign;
public long timeMillis;
public long nonce;
}
@Data
public static class YzmResultBasicDto&lt;T&gt; implements Serializable {
public Integer code;
public String info;
public T data;
}
@Data
public static class YzmTokenDto {
public String accessToken;
public String tokenType;
public Long expiresIn;
public String scope;
}
}
</code></pre>