自行实现签名
签名算法说明
签名算法 | 标准签名算法 | 描述 |
---|---|---|
RSA2 | SHA256WithRSA | 强制要求密钥的长度至少为 2048。 |
自行实现签名
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
appId | String | 必填 | 32 | 资金平台分配给企业的应用编号 | 1633440541561720832 |
systemCode | String | 否 | 16 | 资金平台分配给企业对接系统的编码,部分非查询类接口需要系统来源 | kingdee |
command | String | 必填 | 128 | 接口指令码 | yocyl.basic.organization.create |
format | String | 必填 | 10 | 仅支持JSON | JSON |
charset | String | 必填 | 10 | 仅支持UTF-8 | UTF-8 |
timestamp | String | 必填 | 14 | 发送请求的时间,格式"yyyyMMddHHmmss",请求时间10分钟内有效 | 20210604120100 |
version | String | 必填 | 5 | 调用的接口版本,固定为:1.0.0 | 1.0.0 |
signType | String | 必填 | 5 | 生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 |
sign | String | 必填 | 512 | 请求参数的签名串,详见签名 | |
notifyUrl | String | 可选 | 255 | 主动通知企业业务系统服务器里指定的页面http/https路径。 | http://cfs-test.yocyl.com/receive-notify |
encryptType | String | 可选 | 3 | 加密类型,仅支持AES | AES |
bizContent | String | 可选 | 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递,具体参照各业务接口接入文档 |
生成签名方首先将所有参数和值放入一个map 中,并按照 key 值升序排列(调用Collections.sort 方法)。
然后将所有参数拼接起来,去掉 key 或 value 为空的参数,并用 & 连接,组成签名原文。
第一步:筛选并排序
第二步:拼接
第三步:调用签名函数
示例代码
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.*;
import java.util.stream.Stream;
/**
* 加签工具DEMO
*
* @author Stewart
* @version SignUtil v1.0 2023/6/2
*/
public class SignUtil {
/**
* 在财资云系统配置的私钥字符串,可以使用密钥工具生成
*/
private static final String privateKey = "私钥字符串";
/**
* 字符集编码,默认UTF-8
*/
private static final String charset = "UTF-8";
/**
* 加签Demo
*
* @param requestParams 请求参数
* @return 加签后的字符串
*/
public static String signDemo(Map<String,Object> requestParams) throws Exception {
Map<String, String> filtedMap = filtering(requestParams);
String preSignedStr = getContent(filtedMap);
return signing(preSignedStr);
}
/**
* 筛选
* 将参数转换成String, 并筛选出不为空的键值
*
* @param requestParams 请求参数
* @return 筛选结果
*/
private static Map<String, String> filtering(Map<String,Object> requestParams){
Map<String, String> dataMap = new TreeMap<>();
requestParams.forEach((k,v) -> {
String value = Optional.ofNullable(v).map(e -> {
if(!(e instanceof String)){
//可以替换成任何JSON工具类
return JSONObject.toJSONString(e);
}
return e.toString();
}).orElse(null);
if(isNoneBlank(new CharSequence[]{k, value})){
dataMap.put(k,value);
}
});
return dataMap;
}
/**
* 获取待加签字符串
* 将参数Key排序后放入有序集合
* 通过遍历Key的有序集合,按顺序拿到排序后的Key
* 从参数Map中按Key的顺序取值,并拼接字符串
*
* @param filtedMap 筛选后的请求参数
* @return 待加签字符串
*/
private static String getContent(Map<String, String> filtedMap) {
if (filtedMap == null) {
return null;
} else {
filtedMap.remove("sign");
List<String> keys = new ArrayList(filtedMap.keySet());
Collections.sort(keys);
StringJoiner joiner = new StringJoiner("&");
keys.forEach((key) -> {
String value = filtedMap.get(key);
joiner.add(key.concat("=").concat(value));
});
return joiner.toString();
}
}
/**
* 加密
* 使用RSA2算法对待加密字符串进行加密
*
* @param preSignedStr 待加密字符串
* @return 加密后字符串
* @throws Exception 加密过程中出现异常
*/
private static String signing(String preSignedStr) throws Exception {
PrivateKey priKey = getPrivateKey("RSA", privateKey);
Signature signature = Signature.getInstance("SHA256WithRSA");
return rsaSign(priKey, signature, preSignedStr, charset);
}
/**
* 获取私钥对象
*
* @param algorithm 算法
* @param privateKey 私钥字符串
* @return 私钥对象
* @throws InvalidKeySpecException 密钥不合法异常
* @throws NoSuchAlgorithmException 算法不存在异常
*/
public static PrivateKey getPrivateKey(String algorithm, String privateKey) throws InvalidKeySpecException, NoSuchAlgorithmException {
if (!isEmpty(privateKey) && !isEmpty(algorithm)) {
KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
byte[] encodedKey = Base64.decodeBase64(privateKey);
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
} else {
return null;
}
}
/**
* RSA加密
*
* @param priKey 私钥对象
* @param signature 签名对象
* @param content 待加密内容
* @param charset 字符集
* @return 加密后字符串
* @throws InvalidKeyException 密钥不合法异常
* @throws SignatureException 加签异常
* @throws UnsupportedEncodingException 不支持的字符集异常
*/
private static String rsaSign(PrivateKey priKey, Signature signature, String content, String charset) throws InvalidKeyException, SignatureException, UnsupportedEncodingException {
signature.initSign(priKey);
signature.update(content.getBytes(charset));
byte[] signed = signature.sign();
return Base64.encodeBase64String(signed);
}
/**
* 判断CharSequence是否为空
*
* @param css 字符序列
* @return 是否为空的判断结果
*/
public static boolean isNoneBlank(final CharSequence... css) {
if(css == null){
return false;
}
return Stream.of(css).allMatch(SignUtil::hasText);
}
/**
* 判断字符串是否有内容
*
* @param str 字符串
* @return 是否有内容
*/
public static boolean hasText(CharSequence str) {
return isEmpty(str) && containsText(str);
}
/**
* 是否包含除空格外的内容
*
* @param str 字符串
* @return 是否有内容
*/
private static boolean containsText(CharSequence str) {
long count = str.chars().filter(chr -> !Character.isWhitespace(chr)).count();
if(count>0){
return true;
}
return false;
}
/**
* 判断字符串是不是空字符串
*
* @param str 字符串
* @return 是否为空
*/
public static boolean isEmpty(CharSequence str) {
return str == null || "".contentEquals(str);
}
}
信息
第四步:拼接完整请求
1. 拼接原始数据
appId=2014072300XXXXXX&bizContent={"id":"2022XXXX","totalAmount":88.88,"subject":"SOMETHING","scene":"xxx_code","authCode":"287634438256xxxxx"}&charset=UTF-8&command=xxx.xxx.xxx&signType=RSA2×tamp=2014-07-24 03:07:50&version=1.0&sign=GhT5Q6YxBtvf4q855TiNWEF/DIeLMyEE97+OnJDHpe6joNfr9F3BAE37dczcogU7uLfpNo+dh3cgly05bQN5nTj9TJ3SrBZWa40cltlgkI+6+0FuJoFSA225+MCrD4fNNqi2Zd5Adi9qC9NdN8nBVsjvzqtn5NepOt8dG1qdv7KvVwG74emFea1j2JuMBVBMp2PgIEwCv5c8OJSXa2P66+l3YCqEEMjBN22Dz0chlJ4hKMW3rA2oISH6ryzIvNIiy4/djKEdmxFeBorp1vrbmAtBqE++afggzzGt5w4arkbQ4fWLXJwkC9+1y4irCgmX65CEb5ct+fRgG2/9/B0rYg==
2. Encode 请求数据
说明:将接口业务参数 bizContent 的 value 值作为一个整体进行 encode 操作。
appId=2014072300XXXXXX&bizContent=%7B%22id%22%3A%222022XXXX%22%2C%22totalAmount%22%3A88.88%2C%22subject%22%3A%22SOMETHING%22%2C%22scene%22%3A%22xxx_code%22%2C%22authCode%22%3A%22287634438256xxxxx%22%7D&charset=UTF-8&command=xxx.xxx.xxx&signType=RSA2×tamp=2014-07-24%2003%3A07%3A50&version=1.0&sign=GhT5Q6YxBtvf4q855TiNWEF%2FDIeLMyEE97%2BOnJDHpe6joNfr9F3BAE37dczcogU7uLfpNo%2Bdh3cgly05bQN5nTj9TJ3SrBZWa40cltlgkI%2B6%2B0FuJoFSA225%2BMCrD4fNNqi2Zd5Adi9qC9NdN8nBVsjvzqtn5NepOt8dG1qdv7KvVwG74emFea1j2JuMBVBMp2PgIEwCv5c8OJSXa2P66%2Bl3YCqEEMjBN22Dz0chlJ4hKMW3rA2oISH6ryzIvNIiy4%2FdjKEdmxFeBorp1vrbmAtBqE%2B%2BafggzzGt5w4arkbQ4fWLXJwkC9%2B1y4irCgmX65CEb5ct%2BfRgG2%2F9%2FB0rYg%3D%3D
3. 拼接请求
修改于 2024-11-14 06:50:39