微信开放平台第三方接入授权开发

说在前面

根据产品需求,需要在已有平台上接入微信第三方平台,这也是我第一次开发微信相关内容,在这期间走了不少弯路,今天有点时间写下来,希望能对新的开发者有点帮助,少踩点坑。


解密方式

开放平台和公众平台中都有相关的解密实例代码,但想直接使用的话,还需要进行加工处理,这里贴出我自己用的解密类:

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上,如果刚好能帮到你,记得给个赞鸭!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容