支付寶 v3 驗(yàn)簽如何實(shí)現(xiàn)

上次給大家介紹了 支付寶 v3 自簽名如何實(shí)現(xiàn) ,這次順便再把驗(yàn)簽也寫一下。


為什么要驗(yàn)簽

說起為什么要驗(yàn)簽,如果要詳細(xì)一點(diǎn)解釋的話,可以寫很多很多......

我們就簡單一點(diǎn)來解釋:驗(yàn)簽可以證明接收到的信息是支付寶給我的,不是被人中途攔截篡改數(shù)據(jù)之后再發(fā)給我的。

支付寶的通知分為 「同步通知 」和 「異步通知 」:

  • 「同步通知 」就是我們請求支付寶之后,支付寶返回的數(shù)據(jù)。
  • 「異步通知 」是到達(dá)某些條件之后,支付寶主動(dòng)發(fā)的;更詳細(xì)內(nèi)容可以參考之前我寫的 [手把手|支付寶異步通知如何使用]。

對于這兩種通知我們都需要進(jìn)行驗(yàn)簽處理,才能保證數(shù)據(jù)的準(zhǔn)確性?。?? 很重要!?。?/p>

其實(shí)支付寶給的 SDK 里面也封裝了驗(yàn)簽的方法,并且對同步通知都經(jīng)過了驗(yàn)簽的處理,同步驗(yàn)簽不過的話,接口會直接拋出異常 [sign check fail: check Sign and Data Fail]。

另外 v3 SDK 里面提供的驗(yàn)簽方法名跟 v2 版本是一樣的,大家不想麻煩的話可以直接查看 [SDK如何實(shí)現(xiàn)驗(yàn)簽]。

(大家湊合看,v3 版本好像還沒有完整的驗(yàn)簽示例代碼,也可能是我沒找到 ??)

雖然給了這么多簡單的方法,但是我就是要自!己!寫!一!遍!╭(╯^╰)╮


如何驗(yàn)簽

驗(yàn)簽的流程比加簽要簡單一點(diǎn),下面用支付寶同步通知的數(shù)據(jù)做驗(yàn)簽例子


步驟一、接收支付寶返回的信息

首先就是要接收到支付寶返回的信息,因?yàn)槭峭津?yàn)簽的數(shù)據(jù),直接拿之前自簽名的代碼改一下

驗(yàn)簽我們所需要的數(shù)據(jù)有:

  • aliapy-signature:支付寶生成的簽名內(nèi)容。
  • alipay-timestamp:支付寶應(yīng)答時(shí)間戳。
  • alipay-nonce:支付寶應(yīng)答隨機(jī)串。
  • httpResponseBody:響應(yīng)報(bào)文內(nèi)容,自簽名的 resData 數(shù)據(jù)。

接收代碼

//獲取響應(yīng)的請求頭head
Header[] responseHeader = response.getAllHeaders();
//待驗(yàn)簽數(shù)據(jù)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();    

獲取到的響應(yīng)值

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 {
        // 發(fā)送請求的url
        String url = "https://openapi.alipay.com/v3/alipay/trade/precreate";

        // 發(fā)送請求的內(nèi)容
        String content = "{"out_trade_no":"20181128763521373251698","total_amount":"1","subject":"123","body":"body"}";
        String chearset = "utf-8";
        // 創(chuàng)建請求對象:post或者get
        HttpPost httpPost = new HttpPost(url);
        // httpClient實(shí)例化
        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 設(shè)置類型
        // "application/x-www-form-urlencoded","application/json"、multipart/form-data、text/xml
        httpPost.setHeader("Content-Type", "application/json");
        // 調(diào)用方的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==");
        // 支付寶根證書序列號,使用證書模式時(shí),需要傳遞該值
        // httpPost.setHeader("alipay-root-cert-sn", "");

        // 組織數(shù)據(jù)
        StringEntity se = null;
        try {
            se = new StringEntity(content);
            // 設(shè)置編碼格式
            se.setContentEncoding(chearset);
            // 設(shè)置數(shù)據(jù)類型
            se.setContentType("application/json");
            // post請求,將請求體填充進(jìn)httpPost
            httpPost.setEntity(se);
            // 通過執(zhí)行httpPost獲取實(shí)例
            HttpResponse response = httpClient.execute(httpPost);
            HttpEntity entity = response.getEntity();
            String resData = EntityUtils.toString(entity);
            System.out.println("httpResponseBody:" + resData);

            //獲取響應(yīng)的請求頭head
            Header[] responseHeader = response.getAllHeaders();
            //待驗(yàn)簽數(shù)據(jù)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);
            
            // 關(guān)閉httpClient資源
            httpClient.close();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

步驟二、拼接待驗(yàn)簽內(nèi)容

接收到響應(yīng)數(shù)據(jù)之后,我們需要按照

${alipay-timestamp}\n
${alipay-nonce}\n
${httpResponseBody}\n

規(guī)則,將數(shù)據(jù)組裝起來。

content 組裝示例

【注意:\n 不要丟!】

String content = alipaytimestamp + "\n" + alipaynonce + "\n" + httpResponseBody + "\n";

返回值

1703576825544
73b3422127c9996ad405e77091eef6f4
{"out_trade_no":"20181128763521373251698","qr_code":"https://qr.alipay.com/bax08770vkyzjc0is6ep25ed"}

步驟三、進(jìn)行簽名比對

組裝完待驗(yàn)簽內(nèi)容之后,我們就可以將數(shù)據(jù)進(jìn)行驗(yàn)簽了,其中用到的參數(shù)有:

  • content:上一步獲取到的內(nèi)容。
  • alipaysignature:第一步獲取到的 alipaysignature。
  • publicKey:為支付寶公鑰,在支付寶平臺上傳應(yīng)用公鑰后獲取,參考 [如何獲取支付寶公鑰]。
  • charset:編碼格式,代碼中用的是 UTF-8。

驗(yàn)簽代碼

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 = "驗(yàn)簽失敗,請檢查公鑰格式是否正確。content=" + content + " publicKey=" + publicKey + " reason="
                    + e.getMessage();
            throw new Exception(errorMessage);
        }

    }

返回值

signVerified:true

只有返回 true 才能說明驗(yàn)簽是通過的。


寫在最后

v3 驗(yàn)簽寫完之后,可以看到其實(shí)跟 v2 的驗(yàn)簽方法沒有什么區(qū)別,最大的不同點(diǎn)在于待驗(yàn)簽內(nèi)容,大家都可以試試看,還是挺簡單的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容