微信開放平臺第三方接入授權開發
微信開放平臺第三方接入授權開發
說在前面
根據產品需求,需要在已有平臺上接入微信第三方平臺,這也是我第一次開發微信相關內容,在這期間走了不少彎路,今天有點時間寫下來,希望能對新的開發者有點幫助,少踩點坑。
解密方式
開放平臺和公眾平臺中都有相關的解密例項程式碼,但想直接使用的話,還需要進行加工處理,這裡貼出我自己用的解密類:
package com.cn.controller.weChat.util;
import javax.servlet.http.HttpServletRequest;
/**
* Created by YancyPeng on 2018/10/16.
* 微信訊息加解密工具
*/
public class SignUtil {
private static WXBizMsgCrypt pc;
//在第三方平臺填寫的token,該token可以自己隨意填寫
private static String token = "";
//在第三方平臺填寫的加解密key,這個也是自己隨意填寫,但是key的長度要符合微信規定
private static String encodingAesKey = "XXXXXXXXXX";
//公眾號第三方平臺的appid,不用糾結該appid,在建立完第三方平臺後微信就會給到你
private static String appId = "XXXXXXX";
//微信加密簽名
private static String msg_signature;
//時間戳
private static String timestamp;
//隨機數
private static String nonce;
static {
try {
pc = new WXBizMsgCrypt(token, encodingAesKey, appId);
} catch (AesException e) {
e.printStackTrace();
}
}
/**
* @param request
* @param encryptMsg 加密的訊息
* @return 返回解密後xml格式字串訊息
*/
public static String decryptMsg(HttpServletRequest request, String encryptMsg) {
String result = "";
//獲取微信加密簽名
msg_signature = request.getParameter("msg_signature");
//時間戳
timestamp = request.getParameter("timestamp");
//隨機數
nonce = request.getParameter("nonce");
System.out.println("微信加密簽名為:-----------------" +msg_signature);
try {
result = pc.decryptMsg(msg_signature, timestamp, nonce, encryptMsg);
} catch (AesException e) {
e.printStackTrace();
}
return result;
}
/**
* @param replyMsg 需要加密的xml格式字串
* @return 加密過後的xml格式字串
*/
public static String encryptMsg(String replyMsg) {
try {
replyMsg = pc.encryptMsg(replyMsg, timestamp, nonce);
} catch (AesException e) {
e.printStackTrace();
}
return replyMsg;
}
private SignUtil() {
}
}
這其中用到的相關類就是微信官方提供的示例程式碼,下載即可,接下來進入正文
不用糾結appid、token和加解密key,appid建立完第三方平臺微信就會給到你,token和加解密key都可以隨便填,但是要符合微信的規範
獲取ticket
微信伺服器迴向授權事件接收URL沒隔10分鐘定時推送ticket,在收到ticket後需要進行解密獲取,接收到後必須直接返回success
/**
* @param postdata 微信傳送過來的加密的xml格式資料,通過在建立第三方平臺是填寫的授權事件URL關聯
* 除了接受授權事件(成功授權、取消授權以及授權更新)外,在接受ticket及授權後回撥URI也會用到該方法
* @return 根據微信開放平臺規定,接收到授權事件後只需要直接返回success
*/
@RequestMapping(value = "/event", method = RequestMethod.POST)
// @ApiOperation(value = "接受授權事件通知和ticket", notes = "返回sucess",
// consumes = "application/json", produces = "application/json", httpMethod = "POST")
// @ApiImplicitParams({})
// @ApiResponses({
// @ApiResponse(code = 200, message = "成功", response = JSONObject.class),
// @ApiResponse(code = 500, message = "失敗", response = JSONObject.class)
// })
public String receiveAuthorizedEvent(@RequestBody(required = false) String postdata, HttpServletRequest request) {
System.out.println("呼叫接受授權事件通知的方法 <getAuthorizedEvent> 的入參為:-----------------------" + postdata);
String decryptXml = SignUtil.decryptMsg(request, postdata); // 獲得解密後的xml檔案
String infoType; // 事件型別
try {
authorizedMap = XmlUtil.xmlToMap(decryptXml); // 獲得xml檔案對應的map
System.out.println("解密後的xml檔案為:------" + authorizedMap);
} catch (Exception e) {
e.printStackTrace();
}
if ((infoType = authorizedMap.get("InfoType")).equals("component_verify_ticket")) { //如果是接受ticket
System.out.println("接受到微信傳送的ticket,ticket = " + authorizedMap.get("ComponentVerifyTicket"));
this.setPublicAuthorizedCode(authorizedMap.get("ComponentVerifyTicket")); // 根據ticket去重新整理公共授權碼
} else if (infoType.equals("unauthorized")) { // 接受的是取消授權事件,將微信授權狀態設為3
String authorizerAppid = authorizedMap.get("AuthorizerAppid");
JSONObject params = new JSONObject();
params.put("authorizerAppid", authorizerAppid);
params.put("authorizerState", "3");
int update = iWeChatInfoSV.updateByAuthAppid(params);
System.out.println("微信端取消授權 【0:失敗,1:成功】 update = " + update);
} // 如果是授權成功和更新授權事件,則什麼都不做,在authorizedSuccess中進行處理
return "success";
}
根據ticket、appid和appsecret來獲得token
由於該token的有效時間為2個小時,在我的設計中,資料庫表中有一個token_update_time欄位,每次接收到ticket就取當前時間與updatetime做對比,如果超過1小時50分,就呼叫介面重新獲取token,當然取updatetime操作肯定做了快取0.0
/**
* 重新整理公共授權碼,由於component_access_token需要2個小時重新整理一次,所以需要判斷本地表中存在的第三方介面呼叫憑據updateTime和當前時間的差值
* 如果超過1小時50分就呼叫微信介面更新,否則不做任何操作
*
* @param componentVerifyTicket 根據最近可用的component_verify_ticket來獲得componentAccessToken和preAuthCode
*/
private void setPublicAuthorizedCode(String componentVerifyTicket) {
// 根據tenantId查出 當前公共授權碼錶中的 ComponentVerifyTicket
System.out.println("執行controller層 重新整理公共授權碼的方法 <setPublicAuthorizedCode> 的入參為: componentVerifyTicket = " + componentVerifyTicket);
AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo();
if (null != accessTokenInfo) { // 如果不是首次接受ticket
Long tokenUpdateTime = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).parse(accessTokenInfo.getTokenUpdateTime(),
new ParsePosition(0)).getTime();
Long currentTime = System.currentTimeMillis();
if ((currentTime - tokenUpdateTime) / 1000 >= 6600) { // 如果大於等於1小時50分
// 獲取 component_access_token
JSONObject params = new JSONObject();
params.put("component_verify_ticket", componentVerifyTicket);
params.put("component_appsecret", ComponentAppSecret);
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_component_token", params.toJSONString());
System.out.println("獲取component_access_token的結果為:---------------------" + result);
String componentAccessToken = JSONObject.parseObject(result).getString("component_access_token");
if (!StringUtils.isEmpty(componentAccessToken)) {
// 拼裝引數,新增到本地資料庫
JSONObject tokenParams = new JSONObject();
tokenParams.put("componentVerifyTicket", componentVerifyTicket);
tokenParams.put("componentAccessToken", componentAccessToken);
tokenParams.put("tokenUpdateTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(currentTime));
int update = iAccessTokenInfoSV.updateAccessToken(tokenParams);
System.out.println("更新第三方介面呼叫憑據component_access_token 【0:失敗,1:成功】 update = " + update);
} else {
System.out.println("Controller層執行 《setPublicAuthorizedCode》方法時返回值有錯---------");
}
} // 如果小於則不需要更新
} else { //首次接收ticket,需要走一遍整個流程,獲取component_access_token和pre_auth_code,新增進本地資料庫
// 首先獲取component_access_token
JSONObject params = new JSONObject();
params.put("component_verify_ticket", componentVerifyTicket);
params.put("component_appsecret", ComponentAppSecret);
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_component_token", params.toJSONString());
System.out.println("首次獲取component_access_token的結果為:---------------------" + result);
String componentAccessToken = JSONObject.parseObject(result).getString("component_access_token");
// 獲取pre_auth_code
JSONObject preParams = new JSONObject();
preParams.put("component_appid", ComponentAppId);
result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken, preParams.toJSONString());
System.out.println("首次獲取的pre_auth_code為:------------------------" + result);
String preAuthCode = JSONObject.parseObject(result).getString("pre_auth_code");
// 封裝引數,新增進本地資料庫
if (!StringUtils.isEmpty(componentAccessToken) && !StringUtils.isEmpty(preAuthCode)){
JSONObject tokenParams = new JSONObject();
tokenParams.put("componentVerifyTicket", componentVerifyTicket);
tokenParams.put("componentAccessToken", componentAccessToken);
tokenParams.put("preAuthCode", preAuthCode);
int insert = iAccessTokenInfoSV.insertSelective(tokenParams);
System.out.println("首次新增公共授權碼進本地資料庫 【0:失敗,1:成功】 insert = " + insert);
}else {
System.out.println("首次請求componentAccessToken或者preAuthCode時失敗----------");
}
}
}
根據token來獲得pre_auth_code
預授權碼的有效時間為10分鐘,且該預授權碼只能使用一次,就是說若在10分鐘之內要進行第二次掃碼,就需要呼叫介面重新獲得該預授權碼,這個太坑了,我之前還準備10分鐘之內複用同一個
/**
* 新增授權,預授權碼pre_auth_code 10分鐘更新一次
* 每次請求新增授權都去獲取新的預授權碼 儲存進本地資料庫
*/
@RequestMapping(method = RequestMethod.GET)
// @ApiOperation(value = "新增授權", notes = "重定向到微信授權二維碼頁面",
// consumes = "application/json", produces = "application/json", httpMethod = "GET")
// @ApiImplicitParams({})
// @ApiResponses({
// @ApiResponse(code = 200, message = "成功", response = JSONObject.class),
// @ApiResponse(code = 500, message = "失敗", response = JSONObject.class)
// })
public void authorize() {
// HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
String redirectUrl = "http://XXXXXXX"; // 授權成功回撥url
AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo(); // 獲取公共授權碼物件
Long currentTime = System.currentTimeMillis();
String preAuthCode = "";
String componentAccessToken = accessTokenInfo.getComponentAccessToken();
// 接下來根據component_access_token來獲取預授權碼 pre_auth_code
JSONObject params = new JSONObject();
params.put("component_appid", ComponentAppId);
String result = HttpClientUtil.httpPost("https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode?component_access_token=" + componentAccessToken, params.toJSONString());
preAuthCode = JSONObject.parseObject(result).getString("pre_auth_code");
System.out.println("獲取的pre_auth_code為:------------------------" + preAuthCode);
if (!(StringUtils.isEmpty(preAuthCode))) { // 如果獲取到預授權碼才更新
JSONObject preParams = new JSONObject();
preParams.put("preAuthCode", preAuthCode);
int update = iAccessTokenInfoSV.updateAccessToken(preParams); // 更新本地資料庫
System.out.println("更新預授權碼 【0:失敗,1:成功】 update = " + update);
} else {
System.out.println("Controller層 請求新增授權方法《authorize》時 component_access_token 的值過期了!!!!!!!");
}
try {
response.sendRedirect("https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=" + ComponentAppId
+ "&pre_auth_code=" + preAuthCode + "&redirect_uri=" + redirectUrl);
} catch (IOException e) {
e.printStackTrace();
}
}
引導進入授權頁面
引數為https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx,授權成功後會回撥uri
會將使用者的授權碼authorization_code返回 該授權碼的有效期為10分鐘,這個回撥uri很重要,微信第三方平臺也有一個推送授權相關通知的介面,但是由於業務原因沒有采用該介面(如果有需要了解的可以私聊我或者留言)
當公眾號對第三方平臺進行授權、取消授權、更新授權後,微信伺服器會向第三方平臺方的授權事件接收URL(建立第三方平臺時填寫)推送相關通知。
進行授權:是指進行第一次授權,如果已經授權過再繼續掃碼授權不會觸發
取消授權:是指在微信公眾平臺官網手動取消已經授權的第三方平臺
更新授權:是指在已經進行過第一次授權,再次授權的時候更改已經授權過的許可權集
現在我的實現方式是直接拿到回撥uri中的authorization_code來進行下一步操作因為這個code不管你用不用它都會在授權成功後出現在位址列中
根據authorization_code獲取公眾號授權資訊
這個沒啥說的,官方文件已經寫得很清楚了,這一步獲得了我們最需要的authorizer_appid和authorizer_access_token
通過authorizer_appid可以來獲取公眾號的基本資訊,authorizer_access_token是用來呼叫微信公眾平臺的相關介面
注意:該authorizer_access_token就等同於微信公眾平臺的access_token,不用糾結這個!
微信公眾平臺介面參考官方文件
詳細程式碼在我的github上,如果剛好能幫到你,記得給個贊,O(∩_∩)O哈哈~