在一些項目中,客戶端在調用服務的接口時,通常需要設置簽名驗證,以保證對客戶端的認證。在簽名過程中一般每個公司都有自己的簽名規則和簽名算法,廣泛使用的是使用非對稱加密算法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;
}
到此,服務端的核心代碼完成。
微信搜索黑白色調關注我吧: