微信開放平臺第三方接入授權開發(fā)

說在前面

根據(jù)產(chǎn)品需求执赡,需要在已有平臺上接入微信第三方平臺,這也是我第一次開發(fā)微信相關內(nèi)容函筋,在這期間走了不少彎路,今天有點時間寫下來奠伪,希望能對新的開發(fā)者有點幫助跌帐,少踩點坑。


解密方式

開放平臺和公眾平臺中都有相關的解密實例代碼绊率,但想直接使用的話谨敛,還需要進行加工處理,這里貼出我自己用的解密類:

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的長度要符合微信規(guī)定

    private static String encodingAesKey = "XXXXXXXXXX";

    //公眾號第三方平臺的appid藐俺,不用糾結該appid炊甲,在創(chuàng)建完第三方平臺后微信就會給到你

    private static String appId = "XXXXXXX";

    //微信加密簽名
    private static String msg_signature;
    //時間戳
    private static String timestamp;
    //隨機數(shù)
    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");

        //隨機數(shù)
        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創(chuàng)建完第三方平臺微信就會給到你梭姓,token和加解密key都可以隨便填零抬,但是要符合微信的規(guī)范


獲取ticket

微信服務器回向授權事件接收URL沒隔10分鐘定時推送ticket除师,在收到ticket后需要進行解密獲取,接收到后必須直接返回success

   /**
     * @param postdata 微信發(fā)送過來的加密的xml格式數(shù)據(jù)凝化,通過在創(chuàng)建第三方平臺是填寫的授權事件URL關聯(lián)
     *                 除了接受授權事件(成功授權、取消授權以及授權更新)外官辽,在接受ticket及授權后回調(diào)URI也會用到該方法
     * @return 根據(jù)微信開放平臺規(guī)定蛹磺,接收到授權事件后只需要直接返回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("調(diào)用接受授權事件通知的方法 <getAuthorizedEvent> 的入?yún)椋?----------------------" + 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("接受到微信發(fā)送的ticket,ticket = " + authorizedMap.get("ComponentVerifyTicket"));
            this.setPublicAuthorizedCode(authorizedMap.get("ComponentVerifyTicket")); // 根據(jù)ticket去刷新公共授權碼
        } else if (infoType.equals("unauthorized")) { // 接受的是取消授權事件同仆,將微信授權狀態(tài)設為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";
    }

根據(jù)ticket鳖轰、appid和appsecret來獲得token

由于該token的有效時間為2個小時,在我的設計中扶镀,數(shù)據(jù)庫表中有一個token_update_time字段蕴侣,每次接收到ticket就取當前時間與updatetime做對比,如果超過1小時50分臭觉,就調(diào)用接口重新獲取token昆雀,當然取updatetime操作肯定做了緩存0.0

/**
     * 刷新公共授權碼,由于component_access_token需要2個小時刷新一次蝠筑,所以需要判斷本地表中存在的第三方接口調(diào)用憑據(jù)updateTime和當前時間的差值
     * 如果超過1小時50分就調(diào)用微信接口更新狞膘,否則不做任何操作
     *
     * @param componentVerifyTicket 根據(jù)最近可用的component_verify_ticket來獲得componentAccessToken和preAuthCode
     */
    private void setPublicAuthorizedCode(String componentVerifyTicket) {
        // 根據(jù)tenantId查出 當前公共授權碼表中的 ComponentVerifyTicket
        System.out.println("執(zhí)行controller層 刷新公共授權碼的方法  <setPublicAuthorizedCode> 的入?yún)椋?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)) {
                    // 拼裝參數(shù),添加到本地數(shù)據(jù)庫
                    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("更新第三方接口調(diào)用憑據(jù)component_access_token 【0:失敗什乙,1:成功】 update = " + update);
                } else {
                    System.out.println("Controller層執(zhí)行 《setPublicAuthorizedCode》方法時返回值有錯---------");
                }

            } // 如果小于則不需要更新

        } else { //首次接收ticket挽封,需要走一遍整個流程,獲取component_access_token和pre_auth_code臣镣,添加進本地數(shù)據(jù)庫

            // 首先獲取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");

            // 封裝參數(shù)辅愿,添加進本地數(shù)據(jù)庫
            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("首次添加公共授權碼進本地數(shù)據(jù)庫  【0:失敗,1:成功】 insert = " + insert);
            }else {
                System.out.println("首次請求componentAccessToken或者preAuthCode時失敗----------");
            }
            
        }

    }

根據(jù)token來獲得pre_auth_code

預授權碼的有效時間為10分鐘忆某,且該預授權碼只能使用一次点待,就是說若在10分鐘之內(nèi)要進行第二次掃碼,就需要調(diào)用接口重新獲得該預授權碼弃舒,這個太坑了癞埠,我之前還準備10分鐘之內(nèi)復用同一個

 /**
     * 新增授權,預授權碼pre_auth_code 10分鐘更新一次
     * 每次請求新增授權都去獲取新的預授權碼 保存進本地數(shù)據(jù)庫
     */
    @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"; // 授權成功回調(diào)url
        AccessTokenInfo accessTokenInfo = iAccessTokenInfoSV.selectActInfo(); // 獲取公共授權碼對象

        Long currentTime = System.currentTimeMillis();
        String preAuthCode = "";
        String componentAccessToken = accessTokenInfo.getComponentAccessToken();
        // 接下來根據(jù)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); // 更新本地數(shù)據(jù)庫
            System.out.println("更新預授權碼 【0:失敗聋呢,1:成功】 update = " + update);
        } else {
            System.out.println("Controller層 請求新增授權方法《authorize》時 component_access_token 的值過期了C缱佟!O髅獭M教健!N箍摺测暗!");
        }

        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();
        }
    }


引導進入授權頁面

參數(shù)為https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=xxxx&pre_auth_code=xxxxx&redirect_uri=xxxx央串,授權成功后會回調(diào)uri
會將用戶的授權碼authorization_code返回 該授權碼的有效期為10分鐘,這個回調(diào)uri很重要碗啄,微信第三方平臺也有一個推送授權相關通知的接口质和,但是由于業(yè)務原因沒有采用該接口(如果有需要了解的可以私聊我或者留言)

當公眾號對第三方平臺進行授權、取消授權稚字、更新授權后饲宿,微信服務器會向第三方平臺方的授權事件接收URL(創(chuàng)建第三方平臺時填寫)推送相關通知。
進行授權:是指進行第一次授權胆描,如果已經(jīng)授權過再繼續(xù)掃碼授權不會觸發(fā)
取消授權:是指在微信公眾平臺官網(wǎng)手動取消已經(jīng)授權的第三方平臺
更新授權:是指在已經(jīng)進行過第一次授權瘫想,再次授權的時候更改已經(jīng)授權過的權限集

現(xiàn)在我的實現(xiàn)方式是直接拿到回調(diào)uri中的authorization_code來進行下一步操作因為這個code不管你用不用它都會在授權成功后出現(xiàn)在地址欄中


根據(jù)authorization_code獲取公眾號授權信息

這個沒啥說的,官方文檔已經(jīng)寫得很清楚了昌讲,這一步獲得了我們最需要的authorizer_appid和authorizer_access_token

通過authorizer_appid可以來獲取公眾號的基本信息国夜,authorizer_access_token是用來調(diào)用微信公眾平臺的相關接口

注意:該authorizer_access_token就等同于微信公眾平臺的access_token,不用糾結這個短绸!
微信公眾平臺接口參考官方文檔


詳細代碼在我的github上车吹,如果剛好能幫到你,記得給個贊鴨醋闭!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窄驹,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子证逻,更是在濱河造成了極大的恐慌乐埠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囚企,死亡現(xiàn)場離奇詭異饮戳,居然都是意外死亡,警方通過查閱死者的電腦和手機洞拨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來负拟,“玉大人烦衣,你說我怎么就攤上這事⊙谡悖” “怎么了花吟?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長厨姚。 經(jīng)常有香客問我衅澈,道長,這世上最難降的妖魔是什么谬墙? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任今布,我火速辦了婚禮经备,結果婚禮上,老公的妹妹穿的比我還像新娘部默。我一直安慰自己侵蒙,他們只是感情好,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布傅蹂。 她就那樣靜靜地躺著纷闺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪份蝴。 梳的紋絲不亂的頭發(fā)上犁功,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音婚夫,去河邊找鬼浸卦。 笑死,一個胖子當著我的面吹牛请敦,可吹牛的內(nèi)容都是我干的镐躲。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼侍筛,長吁一口氣:“原來是場噩夢啊……” “哼萤皂!你這毒婦竟也來了?” 一聲冷哼從身側響起匣椰,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤裆熙,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后禽笑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體入录,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年佳镜,在試婚紗的時候發(fā)現(xiàn)自己被綠了僚稿。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡蟀伸,死狀恐怖蚀同,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情啊掏,我是刑警寧澤蠢络,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站迟蜜,受9級特大地震影響刹孔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜娜睛,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一髓霞、第九天 我趴在偏房一處隱蔽的房頂上張望卦睹。 院中可真熱鬧,春花似錦酸茴、人聲如沸分预。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽笼痹。三九已至,卻和暖如春酪穿,著一層夾襖步出監(jiān)牢的瞬間凳干,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工被济, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留救赐,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓只磷,卻偏偏與公主長得像经磅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子钮追,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容