1. 程式人生 > >微信開放平臺第三方接入授權開發

微信開放平臺第三方接入授權開發

微信開放平臺第三方接入授權開發

說在前面

根據產品需求,需要在已有平臺上接入微信第三方平臺,這也是我第一次開發微信相關內容,在這期間走了不少彎路,今天有點時間寫下來,希望能對新的開發者有點幫助,少踩點坑。

解密方式

開放平臺和公眾平臺中都有相關的解密例項程式碼,但想直接使用的話,還需要進行加工處理,這裡貼出我自己用的解密類:

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哈哈~