摘要
本節(jié)首先會講解商戶證書、私鑰、微信平臺證書的獲取、APIv3密鑰的生成。然后將我們微信支付需要的參數(shù)配置信息初始化出來,為后面的業(yè)務(wù)代碼使用。結(jié)合微信平臺證書下載案例和微信統(tǒng)一下單api來講解請求和響應(yīng)都做了什么操作。上一節(jié)中我們提到的那些證書又是如何應(yīng)用在接口中的。最后再講一下如何做內(nèi)外網(wǎng)穿透。
證書獲取
微信平臺證書獲取
為確保 API 請求過程中的安全性,客戶端需要使用微信支付平臺證書來驗(yàn)證服務(wù)器響應(yīng)的真實(shí)性和完整性。微信平臺證書需要我們下載來使用,主要應(yīng)用場景是商戶用來驗(yàn)證簽名、微信側(cè)來解密數(shù)據(jù)使用。服務(wù)端的sdk已經(jīng)為我們提供了下載證書的api。打開服務(wù)端sdk的源碼地址:https://github.com/wechatpay-apiv3/wechatpay-java,我們可以通過手動或自動下載證書的方式來獲取微信平臺證書,如果我們使用的自動下載證書 從 v0.2.3 版本開始,sdk中引入了一個名為 RSAAutoCertificateConfig 的配置類,用于自動更新平臺證書。
RSAAutoCertificateConfig
會利用 AutoCertificateService
自動下載微信支付平臺證書。 AutoCertificateService
將啟動一個后臺線程,定期(目前為每60分鐘)更新證書,以實(shí)現(xiàn)證書過期時的平滑切換。在每次構(gòu)建 RSAAutoCertificateConfig
時,SDK 首先會使用傳入的商戶參數(shù)下載一次微信支付平臺證書。 如果下載成功,SDK 會將商戶參數(shù)注冊或更新至 AutoCertificateService
。若下載失敗,將會拋出異常。
為了提高性能,建議將配置類作為全局變量。 復(fù)用 RSAAutoCertificateConfig
可以減少不必要的證書下載,避免資源浪費(fèi)。 只有在配置發(fā)生變更時,才需要重新構(gòu)造 RSAAutoCertificateConfig
。如果您有多個商戶號,可以為每個商戶構(gòu)建相應(yīng)的 RSAAutoCertificateConfig
。為了保證是全局唯一,下面我們來初始化一個全局配置類,我們使用單例模式來自動更新證書代碼如下:創(chuàng)建一個WxInitUtils.java文件,添加如下代碼:
//商戶的全局配置類
private static Config instance;
/**
* 定義商戶的全局配置信息,要求一個商戶號對應(yīng)一個配置
* 不能重復(fù)生成配置
* RSAAutoCertificateConfig 會利用 AutoCertificateService 自動下載微信支付平臺證書。
* AutoCertificateService 將啟動一個后臺線程,定期(目前為每60分鐘)更新證書,
* 以實(shí)現(xiàn)證書過期時的平滑切換。
* 在每次構(gòu)建 RSAAutoCertificateConfig 時,
* SDK 首先會使用傳入的商戶參數(shù)下載一次微信支付平臺證書。 如果下載成功,SDK 會將商戶參數(shù)注冊或更
* 新至 AutoCertificateService。若下載失敗,將會拋出異常。
* 為了提高性能,建議將配置類作為全局變量。 復(fù)用 RSAAutoCertificateConfig
* 可以減少不必要的證書下載,避免資源浪費(fèi)。 只有在配置發(fā)生變更時,
* 才需要重新構(gòu)造 RSAAutoCertificateConfig。
*/
public static Config getInstance(WxPayConfig wxPayConfig){
if (instance == null){
//如果實(shí)例不存在,創(chuàng)建一個新的實(shí)例
synchronized (WxPayConfig.class){
//雙重檢查鎖定,防止多線程競爭時創(chuàng)建多個實(shí)例
if (instance == null){
try{
if(wxPayConfig == null){
log.info("配置信息加載出錯===");
return null;
}
log.info("商戶號為==="+ wxPayConfig.getMchId());
log.info("商戶私鑰串為==="+ wxPayConfig.getPrivateKey());
log.info("序列號為==="+ wxPayConfig.getMchSerialNo());
log.info("密鑰為==="+ wxPayConfig.getApiV3Key());
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMchId())
.privateKey(wxPayConfig.getPrivateKey())
// .privateKeyFromPath(wxPayConfig.getPrivateKeyPath())
.merchantSerialNumber(wxPayConfig.getMchSerialNo())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
}catch (Exception e){
e.printStackTrace();
log.error("構(gòu)建商戶配置信息出錯,錯誤信息為"+e.getMessage());
return null;
}
}
}
}
return instance;
}
上述代碼instance實(shí)例只會初始化一次。調(diào)用getInstance方法并傳入我們申請證書信息即可完成證書的自動更新和下載。
接下來我們從源碼分析的角度上來分析一下證書的下載過程,分析下載過程主要目的是看看上一節(jié)的密鑰證書是如何應(yīng)用的。我們重點(diǎn)來分析這段代碼塊:
instance = new RSAAutoCertificateConfig.Builder()
.merchantId(wxPayConfig.getMchId())
.privateKey(wxPayConfig.getPrivateKey())
.merchantSerialNumber(wxPayConfig.getMchSerialNo())
.apiV3Key(wxPayConfig.getApiV3Key())
.build();
使用 new RSAAutoCertificateConfig.Builder() 拿到其內(nèi)部靜態(tài)類Builder,Builder類繼承了抽象類 AbstractRSAConfigBuilder,并調(diào)用父類的方法來初始化商戶信息放到AbstractRSAConfigBuilder配置類中。調(diào)用其內(nèi)部的build方法構(gòu)造RSAAutoCertificateProvider.Builder類,同時將商戶信息存儲到RSAAutoCertificateProvider中,并調(diào)用build()方法來下載證書,下載證書的具體步驟:
1、首先租組裝商戶的簽名信息(包括商戶證書的序列號、商戶私鑰、商戶號)如下代碼段:
credential =
new WechatPay2Credential(
requireNonNull(merchantId),
new RSASigner(requireNonNull(merchantSerialNumber), privateKey));
}
2、構(gòu)造httpClient,并將商戶證書、簽名簽證器設(shè)置到httpclient中。
3、請求微信平臺的證書下載地址,進(jìn)行證書下載,構(gòu)造證書下載器:
CertificateDownloader downloader =
new CertificateDownloader.Builder()
.certificateHandler(rsaCertificateHandler)
.downloadUrl(REQUEST_URL)
.aeadCipher(aeadCipher)
.httpClient(httpClient)
.build();
其中 REQUEST_URL = "https://api.mch.weixin.qq.com/v3/certificates?algorithm_type=RSA"
, aeadCipher
為APIv3密鑰。構(gòu)造的httpClient
4、證書下載:
/**
* 注冊證書下載任務(wù) 如果是第一次注冊,會先下載證書。如果能成功下載,再保存下載器,供定時更新證書使用。如果下載失敗,會拋出異常。
* 如果已經(jīng)注冊過,當(dāng)前傳入的下載器將覆蓋之前的下載器。如果當(dāng)前下載器不能下載證書,定時更新證書會失敗。
*
* @param merchantId 商戶號
* @param type 調(diào)用方自定義的證書類型,例如 RSA/ShangMi
* @param downloader 證書下載器
*/
public static void register(String merchantId, String type, CertificateDownloader downloader) {
String key = calculateDownloadWorkerMapKey(merchantId, type);
Runnable worker =
() -> {
Map<String, X509Certificate> result = downloader.download();
certificateMap.put(key, result);
};
// 下載證書,以驗(yàn)證配置是正確的
// 如果錯誤將拋出異常,fast-fail
worker.run();
// 更新配置
downloadWorkerMap.put(key, worker);
start(defaultUpdateInterval);
}
打開download方法:
/** 下載證書 */
public Map<String, X509Certificate> download() {
HttpRequest httpRequest =
new HttpRequest.Builder()
.httpMethod(HttpMethod.GET)
.url(downloadUrl)
.addHeader(Constant.ACCEPT, " */*")
.addHeader(Constant.CONTENT_TYPE, MediaType.APPLICATION_JSON.getValue())
.build();
HttpResponse<DownloadCertificateResponse> httpResponse =
httpClient.execute(httpRequest, DownloadCertificateResponse.class);
Map<String, X509Certificate> downloaded = decryptCertificate(httpResponse);
validateCertificate(downloaded);
return downloaded;
}
這里大家注意,重點(diǎn)來了,在下載證書時,sdk將簽名封裝到了httpClient中,在 HttpResponse<DownloadCertificateResponse> httpResponse = httpClient.execute(httpRequest, DownloadCertificateResponse.class);
這段代碼中的execute方法中通過構(gòu)造token值,并將token放到請求的header中,重點(diǎn)看這個方法getAuthorization(httpRequest)
這個方法中,使用了商戶的私鑰將請求body進(jìn)行簽名,并將明文、商戶的公鑰、簽名信息組成token串發(fā)送給了微信平臺,具體構(gòu)造token的代碼塊為:
String token =
"mchid=\""
+ getMerchantId()
+ "\","
+ "nonce_str=\""
+ nonceStr
+ "\","
+ "timestamp=\""
+ timestamp
+ "\","
+ "serial_no=\""
+ signature.getCertificateSerialNumber()
+ "\","
+ "signature=\""
+ signature.getSign()
+ "\"";
請求完成后,微信平臺側(cè)會實(shí)時將響應(yīng)數(shù)據(jù)返回。在商戶側(cè)調(diào)用decryptCertificate(httpResponse)
將響應(yīng)的數(shù)據(jù)進(jìn)行解密,解密時使用我們之前設(shè)置的APIv3密鑰。緊接著會驗(yàn)證下載證書的有效性會調(diào)用 validateCertificate(downloaded);
具體的驗(yàn)證代碼段如下:
PKIXParameters params = new PKIXParameters(trustAnchor);
params.setRevocationEnabled(false);
List<X509Certificate> certs = new ArrayList<>();
certs.add(certificate);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath certPath = cf.generateCertPath(certs);
CertPathValidator validator = CertPathValidator.getInstance("PKIX");
validator.validate(certPath, params);
上述代碼塊用于檢查證書是否由可信的根證書頒發(fā),以及證書是否有效。X509Certificate是一種常用的數(shù)字證書標(biāo)準(zhǔn)。首先創(chuàng)建一個 PKIXParameters 類型的對象,它是一個用于驗(yàn)證證書路徑的參數(shù)集合,它的構(gòu)造器需要一個 trustAnchor 參數(shù),表示可信的根證書,它是一個 Set 類型的對象,它包含了一個或多個根證書的信息。 getInstance(“PKIX”) 返回一個支持 PKIX(公鑰基礎(chǔ)設(shè)施)算法的驗(yàn)證器實(shí)例。
證書下載完成會存放到 AutoCertificateService類certificateMap中 ,certificateMap 是一個ConcurrentHashMap ,是線程安全的,可以支持多線程的并發(fā)訪問和修改,而AutoCertificateService類是一個定時更新證書的服務(wù),它是一個由靜態(tài)函數(shù)構(gòu)成的工具類。
private static final ConcurrentHashMap<String, Map<String, X509Certificate>> certificateMap =
new ConcurrentHashMap<>();
Map<String, X509Certificate> result = downloader.download();
certificateMap.put(key, result);
到此微信平臺證書的自動下載過程就描述清楚了,下面我們來總結(jié)一下具體流程:
(1)商戶側(cè)包裝商戶信息:商戶號、商戶公鑰、商戶私鑰等信息,并將請求的數(shù)據(jù)進(jìn)行簽名。將簽名和公鑰、時間戳、隨機(jī)數(shù)等信息打包成token放到請求頭部發(fā)送至微信平臺側(cè)。
(2)微信測收到請求后獲取到認(rèn)證token,并解析出商戶公鑰和明文信息,對數(shù)據(jù)進(jìn)行驗(yàn)簽。同時使用APIv3密鑰將證書進(jìn)行加密同步返回給商戶側(cè)。
(3)商戶側(cè)收到微信側(cè)的響應(yīng)數(shù)據(jù)后,從響應(yīng)數(shù)據(jù)解密出證書,應(yīng)答報文解密后,生成X.509證書對象,也就是將證書從String轉(zhuǎn)為X509Certificate。
(4)驗(yàn)證證書的有效性
(5)將證書存儲到AutoCertificateService中的certificateMap 中。
APIv3密鑰設(shè)置
在商戶平臺中【微信支付商戶平臺 - 賬戶中心 - 賬戶設(shè)置 - API安全 - APIv3密鑰設(shè)置】,這個可使用在線生成器生成地址:https://www.bchrt.com/tools/suijimima/
商戶平臺證書和私鑰的獲取
以下內(nèi)容來自微信官網(wǎng):https://kf.qq.com/faq/161222NneAJf161222U7fARv.html
1、登錄【微信支付商戶平臺 - 賬戶中心 - 賬戶設(shè)置 - API安全 - 申請API證書】申請證書,確定后請勿關(guān)閉頁面
2、點(diǎn)擊下載證書工具;下載后,雙擊“WXCertUtil.exe”文件,選擇安裝路徑后,點(diǎn)擊申請證書
也可通過以下鏈接下載證書工具:
windows版本
mac版本
3、在【證書工具】,填寫商戶號信息(商戶號、商戶名稱),點(diǎn)擊下一步
4、在【證書工具】,復(fù)制證書請求串
(若提示"請粘貼請求串到商戶平臺獲取證書串",請?jiān)诘?點(diǎn)步驟檢查是否已粘貼??赏瑫r嘗試手動鼠標(biāo)復(fù)制粘貼的方法)
5、在【商戶平臺】,粘貼證書請求串
6、在【商戶平臺】,輸入操作密碼,安全驗(yàn)證后生成證書串
7、在【商戶平臺】,復(fù)制證書串
8、在【證書工具】,粘貼證書串,點(diǎn)擊下一步,申請證書成功
(若提示"證書與本地公私鑰不匹配",可能是瀏覽器禁用了剪切板復(fù)制功能。請?jiān)诓僮鞑襟E第7點(diǎn),操作時使用鼠標(biāo)選中全部證書串內(nèi)容(注意右邊有下拉框),單擊鼠標(biāo)右鍵選擇復(fù)制)
提醒:請將生成的證書文件轉(zhuǎn)交給技術(shù)人員,由技術(shù)人員將證書部署到服務(wù)器上(請務(wù)必妥善保管證書及私鑰,因?yàn)樗借€文件只能通過證書工具導(dǎo)出,若私鑰丟失,則無法找回,只能作廢后重新申請。)
9、證書申請成功后,在證書文件夾中解壓文件會發(fā)現(xiàn)有3個文件:.p12 這個不用管,其中兩個文件apiclient_cert.pem(商戶公鑰文件)、apiclient_key(商戶私鑰文件)。
到此,商戶API證書已經(jīng)獲取完畢。
初始化微信配置
為了統(tǒng)一配置微信支付用到的信息,我們在項(xiàng)目的resource目錄下創(chuàng)建一個配置文件wxpay.properties
# 商戶號
wxpay.mch-id=xxxxxxx
# 微信商戶證書序列號
wxpay.mch-serial-no=xxxxxxxxxxxxxxxxxxx
# 商戶私鑰(路徑加載方式使用)
wxpay.private-key-path=apiclient_key.pem
# APIv3密鑰
wxpay.api-v3-key=xxxxxxxxxxxxxx
# 小程序APPID
wxpay.appid=xxxxxxxxxxx
# 微信小程序密鑰
wxpay.appSecret=xxxxxxxxxxxxxxxxxxxxxxxxx
# 微信商戶平臺域名(調(diào)用接口時的通用域名)
wxpay.domain=https://api.mch.weixin.qq.com
#微信支付回調(diào)地址域名(需要https 并且為備案的域名)
wxpay.notify-domain=https://test.notify.com
#商戶私鑰
wxpay.private-key=-----BEGIN PRIVATE KEY-----xxxx-----END PRIVATE KEY-----
其對應(yīng)的配置類如下:
@Configuration
@PropertySource("classpath:wxpay.properties") //讀取配置文件
@ConfigurationProperties(prefix="wxpay") //讀取wxpay節(jié)點(diǎn)
@Data //使用set方法將wxpay節(jié)點(diǎn)中的值填充到當(dāng)前類的屬性中
public class WxPayConfig {
// 商戶號
private String mchId;
// 商戶API證書序列號
private String mchSerialNo;
// 商戶私鑰文件
private String privateKeyPath;
// APIv3密鑰
private String apiV3Key;
// APPID
private String appid;
//小程序密鑰
private String appSecret;
// 微信服務(wù)器地址
private String domain;
// 接收結(jié)果通知地址
private String notifyDomain;
// APIv2密鑰
private String partnerKey;
//商戶私鑰字符串
private String privateKey;
}
小程序統(tǒng)一下單
本節(jié),主要來以分析小程序統(tǒng)一下單的案例,來說明sdk的使用方式,以及商戶證書和平臺證書的在調(diào)用下單接口時是如何使用的,本節(jié)不會過多在當(dāng)前項(xiàng)目中展開業(yè)務(wù)代碼,只講解下單接口如何調(diào)用,安全驗(yàn)證的過程。
在調(diào)用小程序下單接口之前,首先要創(chuàng)建訂單數(shù)據(jù),調(diào)用下單接口時將訂單數(shù)據(jù)傳過去,比如訂單金額、商品id、訂單編號等。微信收到訂單后會返回預(yù)付訂單id以及微信平臺返回的微信訂單信息,下面來看看具體的調(diào)用代碼,下面是小程序下單的代碼塊。
if (config == null){
config = WxInitUtils.getInstance(wxPayConfig);
}
JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
// 填充預(yù)下單參數(shù)
PrepayRequest request = new PrepayRequest();
//appid
request.setAppid(wxPayConfig.getAppid());
//商戶id
request.setMchid(wxPayConfig.getMchId());
//產(chǎn)品描述
assert orderInfo != null;
request.setDescription(orderInfo.getOrderTitle());
//商戶訂單號
request.setOutTradeNo(orderInfo.getOrderNo());
//通知url
request.setNotifyUrl(wxPayConfig.getNotifyDomain().concat(WxNotifyType.NATIVE_NOTIFY.getType()));
//金額信息
Amount amount = new Amount();
amount.setTotal(orderInfo.getTotalFee());
amount.setCurrency("CNY");
request.setAmount(amount);
//用戶信息
Payer payer = new Payer();
payer.setOpenid(openId.toString());
request.setPayer(payer);
log.info("請求參數(shù) ===> {}" + request.toString());
PrepayWithRequestPaymentResponse response = null;
try {
// response包含了調(diào)起支付所需的所有參數(shù),可直接用于前端調(diào)起支付
response = service.prepayWithRequestPayment(request);
} catch (Exception e) {
log.error("請求下單失敗,錯誤信息" + e.getMessage());
throw new RuntimeException("請求下單失敗,錯誤信息" + e.getMessage());
}
//處理返回值
assert response != null;
String packageVal = response.getPackageVal();
String prepayId = packageVal;
上述代碼中,首先拿到配置信息config,然后調(diào)用JsapiServiceExtension service = new JsapiServiceExtension.Builder().config(config).build();
這個是sdk提供的api,接下來組裝好參數(shù)之間向微信發(fā)起請求,請求的代碼段response = service.prepayWithRequestPayment(request);
在prepayWithRequestPayment方法中調(diào)用了String prepayId = this.jsapiService.prepay(request).getPrepayId(); 在prepay方法中才是真正的發(fā)起請求,重點(diǎn)來看下請求的代碼段:
HttpRequest httpRequest = (new HttpRequest.Builder()).httpMethod(HttpMethod.POST).url(requestPath).headers(headers).body(this.createRequestBody(request)).build();
HttpResponse<PrepayResponse> httpResponse = this.httpClient.execute(httpRequest, PrepayResponse.class);
在execute方法中又調(diào)用了如下代碼,和下載證書的接口調(diào)用是一樣的。
HttpRequest innerRequest =
new Builder()
.url(httpRequest.getUrl())
.httpMethod(httpRequest.getHttpMethod())
.headers(httpRequest.getHeaders())
.addHeader(AUTHORIZATION, getAuthorization(httpRequest))
.addHeader(USER_AGENT, getUserAgent())
.body(httpRequest.getBody())
.build();
OriginalResponse originalResponse = innerExecute(innerRequest);
validateResponse(originalResponse);
重點(diǎn)來關(guān)注Authorization, 的組成如下:
Authorization: 認(rèn)證類型 簽名信息
具體組成為:
認(rèn)證類型: 目前為WECHATPAY2-SHA256-RSA2048
簽名信息
發(fā)起請求的商戶(包括直連商戶、服務(wù)商或渠道商)的商戶號mchid
商戶API證書序列號serial_no,用于聲明所使用的證書
請求隨機(jī)串nonce_str
時間戳timestamp
簽名值signature
示例如下:
‘Authorization: WECHATPAY2-SHA256-RSA2048 mchid=“1900009191”,nonce_str=“593BEC0C930BF1AFEB40B4A08C8FB242”,signature=“uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==”,timestamp=“1554208460”,serial_no=“1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C”’
以上就是簽名的過程,使用商戶私鑰簽名,簽名后之間發(fā)送到了微信平臺,所以小程序統(tǒng)一下單,并沒有加密的操作,只有簽名的操作。同樣微信同步響應(yīng)后,也需要在商戶側(cè)進(jìn)行驗(yàn)簽。
sdk中驗(yàn)簽的代碼段如下:
String timestamp = responseHeaders.getHeader(WECHAT_PAY_TIMESTAMP);
try {
Instant responseTime = Instant.ofEpochSecond(Long.parseLong(timestamp));
// 拒絕過期請求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes()
>= RESPONSE_EXPIRED_MINUTES) {
throw new IllegalArgumentException(
String.format(
"Validate http response,timestamp[%s] of httpResponse is expires, "
+ "request-id[%s]",
timestamp, responseHeaders.getHeader(REQUEST_ID)));
}
} catch (DateTimeException | NumberFormatException e) {
throw new IllegalArgumentException(
String.format(
"Validate http response,timestamp[%s] of httpResponse is invalid, request-id[%s]",
timestamp, responseHeaders.getHeader(REQUEST_ID)));
}
String message =
timestamp
+ "\n"
+ responseHeaders.getHeader(WECHAT_PAY_NONCE)
+ "\n"
+ (responseBody == null ? "" : responseBody)
+ "\n";
logger.debug("Message for verifying signatures is[{}]", message);
String serialNumber = responseHeaders.getHeader(WECHAT_PAY_SERIAL);
logger.debug("SerialNumber for verifying signatures is[{}]", serialNumber);
String signature = responseHeaders.getHeader(WECHAT_PAY_SIGNATURE);
logger.debug("Signature for verifying signatures is[{}]", signature);
return verifier.verify(serialNumber, message, signature);
驗(yàn)證簽名要經(jīng)過以下幾個步驟:
(1)校驗(yàn)微信響應(yīng)的時間戳是否過期,有效時間為5分鐘,// 拒絕過期請求
if (Duration.between(responseTime, Instant.now()).abs().toMinutes()
>= RESPONSE_EXPIRED_MINUTES) 目的是防止重放攻擊,重放攻擊 是指攻擊者截取報文及其簽名,并以惡意或欺詐目的重新傳輸數(shù)據(jù)的一種攻擊手段。 為了降低此類攻擊的風(fēng)險,微信支付在 HTTP 頭 Wechatpay-Timestamp 中提供了生成簽名的時間戳。若微信支付需要重新發(fā)送某個通知回調(diào),我們也會重新生成相應(yīng)的時間戳和簽名。在驗(yàn)證簽名之前,商戶系統(tǒng)應(yīng)檢查時間戳是否已過期。我們建議商戶系統(tǒng)允許最多5分鐘的時間偏差。如果時間戳與當(dāng)前時間的偏差超過5分鐘,您應(yīng)拒絕處理當(dāng)前的響應(yīng)或回調(diào)通知。
(2)構(gòu)造驗(yàn)簽名串
構(gòu)造驗(yàn)簽名串由3個部分組成 ,應(yīng)答或通知回調(diào)中獲取以下信息:
HTTP 頭 Wechatpay-Timestamp 中的應(yīng)答時間戳
HTTP 頭 Wechatpay-Nonce 中的應(yīng)答隨機(jī)串
應(yīng)答報文主體(Response Body),請使用原始報文主體執(zhí)行驗(yàn)簽。如果您使用了某個框架,要確保它不會篡改報文主體。對報文主體的任何篡改都會導(dǎo)致驗(yàn)證失敗。
(3)驗(yàn)證微信平臺證書是否正確
檢查 HTTP 頭 Wechatpay-Serial 的內(nèi)容是否跟商戶當(dāng)前所持有的微信支付平臺證書的序列號一致。若不一致,請重新獲取證書。否則,簽名的私鑰和證書不匹配,將驗(yàn)證失敗。重點(diǎn)代碼如下:根據(jù)頭部返回的證書序列號查詢下載時存放的證書是否一致。文章來源:http://www.zghlxwxcb.cn/news/detail-777693.html
/**
* 根據(jù)證書序列號獲取證書
*
* @param serialNumber 微信支付平臺證書序列號
* @return X.509證書實(shí)例
*/
@Override
public X509Certificate getCertificate(String serialNumber) {
return AutoCertificateService.getCertificate(merchantId, ALGORITHM_TYPE, serialNumber);
}
以上就是小程序下單的加簽、驗(yàn)簽過程,復(fù)雜的過程都封裝到了sdk中,我們可以不做過多的關(guān)注,但是一定要清楚其中的原理,可以發(fā)現(xiàn),下單的過程并沒有加密、解密的邏輯。文章來源地址http://www.zghlxwxcb.cn/news/detail-777693.html
到了這里,關(guān)于第五節(jié)、項(xiàng)目支付功能實(shí)戰(zhàn)-證書獲取、微信支付集成初始化配置、sdk統(tǒng)一下單、api安全源碼解讀的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!