上次給大家介紹了 支付寶 v3 自簽名如何實現 ,這次順便再把驗籤也寫一下。
為什麼要驗籤
説起為什麼要驗籤,如果要詳細一點解釋的話,可以寫很多很多......
我們就簡單一點來解釋:驗籤可以證明接收到的信息是支付寶給我的,不是被人中途攔截篡改數據之後再發給我的。
支付寶的通知分為 「同步通知 」和 「異步通知 」:
- 「同步通知 」就是我們請求支付寶之後,支付寶返回的數據。
- 「異步通知 」是到達某些條件之後,支付寶主動發的;更詳細內容可以參考之前我寫的 [手把手|支付寶異步通知如何使用]。
對於這兩種通知我們都需要進行驗籤處理,才能保證數據的準確性!(⚠️ 很重要!!)
其實支付寶給的 SDK 裏面也封裝了驗籤的方法,並且對同步通知都經過了驗籤的處理,同步驗籤不過的話,接口會直接拋出異常 [sign check fail: check Sign and Data Fail]。
另外 v3 SDK 裏面提供的驗籤方法名跟 v2 版本是一樣的,大家不想麻煩的話可以直接查看 [SDK如何實現驗籤]。
(大家湊合看,v3 版本好像還沒有完整的驗籤示例代碼,也可能是我沒找到 😢)
雖然給了這麼多簡單的方法,但是我就是要自!己!寫!一!遍!╭(╯^╰)╮
如何驗籤
驗籤的流程比加簽要簡單一點,下面用支付寶同步通知的數據做驗籤例子
步驟一、接收支付寶返回的信息
首先就是要接收到支付寶返回的信息,因為是同步驗籤的數據,直接拿之前自簽名的代碼改一下
驗籤我們所需要的數據有:
- aliapy-signature:支付寶生成的簽名內容。
- alipay-timestamp:支付寶應答時間戳。
- alipay-nonce:支付寶應答隨機串。
- httpResponseBody:響應報文內容,自簽名的 resData 數據。
接收代碼
//獲取響應的請求頭head
Header[] responseHeader = response.getAllHeaders();
//待驗籤數據head:aliapy-signature、alipay-timestamp、alipay-nonce
String alipaysignature = response.getFirstHeader("alipay-signature").getValue();
String alipaytimestamp = response.getFirstHeader("alipay-timestamp").getValue();
String alipaynonce = response.getFirstHeader("alipay-nonce").getValue();
獲取到的響應值
httpResponseBody:{"out_trade_no":"20181128763521373251698","qr_code":"https://qr.alipay.com/bax04870evi3w2dlaeai2502"}
aliapy-signature:M/6yx2OajiQD0mM9Tk9ShsduFERtmj+xI0BN8QiZk8BMUCvMQCne1n/VIbMZ738k4No8nsE1DC0saPe2NqtmgxC3B+TmWgrhJ+4JOVEc7K4/LcIDWN2PaPCw5g5+oUQRIGCbo0+f9yqSew4NwETV2RiVIw91q+kJ4OeIpauSnGQAuwOxqciDM52k7gUhij8G+evhK7xn6TNhiQgRk0RjkyhEEp/00lYb5xI2d9Oj5KgsDC9KTRo9SO0SJaH0SbfNHU40XUkkomuj6jiOEeccfB6Fofzq5jfL3u24Ev9SxTDf2kYZzffShLrYhlrI8947VqC3h8/F6O8y4K/PQl3LCw==
alipay-timestamp:1703576825544
alipay-nonce:73b3422127c9996ad405e77091eef6f4
包含之前自簽名的完整代碼(僅供參考)
public class V3HttpPostTest {
public static void main(String args[]) throws Exception {
// 發送請求的url
String url = "https://openapi.alipay.com/v3/alipay/trade/precreate";
// 發送請求的內容
String content = "{"out_trade_no":"20181128763521373251698","total_amount":"1","subject":"123","body":"body"}";
String chearset = "utf-8";
// 創建請求對象:post或者get
HttpPost httpPost = new HttpPost(url);
// httpClient實例化
CloseableHttpClient httpClient = HttpClients.createDefault();
// 設置類型
// "application/x-www-form-urlencoded","application/json"、multipart/form-data、text/xml
httpPost.setHeader("Content-Type", "application/json");
// 調用方的requestId,用於定位一次請求,需要每次請求保持唯一。
httpPost.setHeader("alipay-request-id", "32432432432423421");
httpPost.setHeader("authorization",
"ALIPAY-SHA256withRSA app_id=2021111111111122,timestamp=1702452177941,nonce=3246658768654544,sign=WDF6pS2qK/kEZnsJDMrhNmd/z82ClZ+VMohYxIUs3MZ2j0m+4reQtSBGa6mZyA5ffbIPPvZTRO+1DLEuuCvZRMQGK3okYSA/ASP7GEqfCDeKmkqzKV2kWrmftNfO+EiIiCnsiyJG4SQ9G7s0OtmCT6wVkphW9wgk7mfUoF5a+Wo3kzvEur3U+7ZfSgLa4HXQG2xE+z7BjmHG8j1qVoVa/3TR1lVBAqOwkodZ9cSPKceK2RxaPkk8gsFbofbuARl5xBqDwkS2caTQu27+DLXT/QJOHRHRw5VtH9v8B7nT+nrijFjktm6hD7aIHuPon6TtEgnbtWltRizEZldh+Fo1Eg==");
// 支付寶根證書序列號,使用證書模式時,需要傳遞該值
// httpPost.setHeader("alipay-root-cert-sn", "");
// 組織數據
StringEntity se = null;
try {
se = new StringEntity(content);
// 設置編碼格式
se.setContentEncoding(chearset);
// 設置數據類型
se.setContentType("application/json");
// post請求,將請求體填充進httpPost
httpPost.setEntity(se);
// 通過執行httpPost獲取實例
HttpResponse response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
String resData = EntityUtils.toString(entity);
System.out.println("httpResponseBody:" + resData);
//獲取響應的請求頭head
Header[] responseHeader = response.getAllHeaders();
//待驗籤數據head:aliapy-signature、alipay-timestamp、alipay-nonce
String alipaysignature = response.getFirstHeader("alipay-signature").getValue();
String alipaytimestamp = response.getFirstHeader("alipay-timestamp").getValue();
String alipaynonce = response.getFirstHeader("alipay-nonce").getValue();
System.out.println("aliapy-signature:" + alipaysignature);
System.out.println("alipay-timestamp:" + alipaytimestamp);
System.out.println("alipay-nonce:" + alipaynonce);
// 關閉httpClient資源
httpClient.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
步驟二、拼接待驗籤內容
接收到響應數據之後,我們需要按照
${alipay-timestamp}\n
${alipay-nonce}\n
${httpResponseBody}\n
規則,將數據組裝起來。
content 組裝示例
【注意:\n 不要丟!】
String content = alipaytimestamp + "\n" + alipaynonce + "\n" + httpResponseBody + "\n";
返回值
1703576825544
73b3422127c9996ad405e77091eef6f4
{"out_trade_no":"20181128763521373251698","qr_code":"https://qr.alipay.com/bax08770vkyzjc0is6ep25ed"}
步驟三、進行簽名比對
組裝完待驗籤內容之後,我們就可以將數據進行驗簽了,其中用到的參數有:
- content:上一步獲取到的內容。
- alipaysignature:第一步獲取到的 alipaysignature。
- publicKey:為支付寶公鑰,在支付寶平台上傳應用公鑰後獲取,參考 [如何獲取支付寶公鑰]。
- charset:編碼格式,代碼中用的是 UTF-8。
驗籤代碼
private static boolean doVerify(String content, String alipaysignature, String publicKey, String charset) throws Exception {
try {
byte[] encodedKey = publicKey.getBytes();
encodedKey = Base64.getDecoder().decode(encodedKey);
PublicKey pubKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(encodedKey));
java.security.Signature signature = java.security.Signature.getInstance("SHA256withRSA");
signature.initVerify(pubKey);
signature.update(content.getBytes(charset));
boolean signVerified = signature.verify(Base64.getDecoder().decode(alipaysignature.getBytes()))
System.out.println("signVerified:" + signVerified);
return falg;
} catch (Exception e) {
String errorMessage = "驗籤失敗,請檢查公鑰格式是否正確。content=" + content + " publicKey=" + publicKey + " reason="
+ e.getMessage();
throw new Exception(errorMessage);
}
}
返回值
signVerified:true
只有返回 true 才能説明驗籤是通過的。
寫在最後
v3 驗簽寫完之後,可以看到其實跟 v2 的驗籤方法沒有什麼區別,最大的不同點在於待驗籤內容,大家都可以試試看,還是挺簡單的。