在一些项目中,客户端在调用服务的接口时,通常需要设置签名验证,以保证对客户端的认证。在签名过程中一般每个公司都有自己的签名规则和签名算法,广泛使用的是使用非对称加密算法RSA为核心,在客户端使用私钥对参数进行加密生成一个密钥,传给服务端,然后在服务端再对这个这个密钥使用公钥进行解密,解密出来的字符串参数与客户端传过来的参数进行对比,如果一样,验证成功。
总结起来就是:
-
客户端
-
首先获取所有的参数,然后对他们进行排序,生成一个字符串
-
对这个字符串MD5加密,然后转为大写
-
然后使用私钥对MD5再次加密,生成最终的签名sign
-
把这个签名sign传给服务端
-
-
服务端
-
获取所有的参数
-
把参数中签名sign参数去除,然后排序,生成一个字符串
-
对这个字符串MD5加密,然后转为大写
-
使用公钥对sign字符串进行解密获取一个String,然后和第三步中获取的字符串相对,如果相等,则验证成功
-
下面我们就通过以上规则实现客户端的签名和服务端的验证。
客户端签名
比如我们这是我们的url:
http://localhost:8080/task/test?name=张三&age=8×tamp=1591261543133
可以看到url中有三个参数,分别是姓名、年龄和时间戳。首先我们要对这些参数进行排序:
public static String getSortedContent(Map<String, String> data) {
StringBuffer content = new StringBuffer();
List<String> keys = new ArrayList<String>(data.keySet());
Collections.sort(keys);
int index = 0;
for (String key : keys) {
String value = data.get(key);
content.append((index == 0 ? "" : "&")).append(key).append("=").append(value);
index++;
}
return content.toString();
}
然后对排序好的字符串进行加密,然后转大写:
String summary = DigestUtils.md5Hex(data).toLowerCase();
最后对summary进行私钥加密:
public static String EncryptByRSAPriKey(String content,String priKey) throws Exception {
try {
PrivateKey privateKey = SecurityUtil.getRSAPriKey(priKey);
Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
cipher.update(content.getBytes(SecurityUtil.RSA_CHARSET));
return SecurityUtil.encodeBase64(cipher.doFinal());
} catch (Exception e) {
e.printStackTrace();
throw new Exception();
}
}
最终得到我们想要的sign,传给服务端。
服务端验证
首先获取到所有的参数后,去除sign,对剩下的参数排序,MD5加密,转大写,
/**
*
* @param params 去除sign的所有参数
* @param sign
* @param pubKey 公钥
* @return
* @throws ApiError
*/
public static boolean verifySign( Map<String, String> params,String sign,String pubKey) throws ApiError {
if (StringUtil.isEmpty(sign)) {
return false;
}
String signType = params.get(Constants.FN_SIGN_TYPE);;
//暂不支持非RSA的签名
if (StringUtil.isEmpty(signType) || !signType.equals(Constants.SIGN_TYPE_RSA)) {
return false;
}
//参与签名的数据
String data = ApiUtils.getSortedContent(params);
ApiLogger.getLogger().debug("sign data:" + data);
String summary = DigestUtils.md5Hex(data).toLowerCase();
ApiLogger.getLogger().debug("sign summary:" + summary);
String summaryDecode = null;
try {
summaryDecode = SecurityUtil.DecryptByRSAPubKey(sign, pubKey);
} catch (Exception e) {
throw new ApiError("do_digest_error", e);
}
return summary.equals(summaryDecode);
}
其中SecurityUtil.DecryptByRSAPubKey(sign, pubKey)的方法为:
/**
*
* 描述:将字符串通过RSA算法公钥解密
* @author wangbing
* @since
* @param content 需要解密的内容
* @param pubKey 公钥
* @return 解密后字符串
* @throws Exception
*/
public static String DecryptByRSAPubKey(String content, String pubKey){
try {
PublicKey publicKey = SecurityUtil.getRSAPubKey(priKey);
Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, publicKey);
cipher.update(SecurityUtil.decodeBase64(content));
return new String(cipher.doFinal(), SecurityUtil.RSA_CHARSET);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
到此,服务端的核心代码完成。