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

说在前面

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


解密方式

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

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("调用接受授权事件通知的方法  的入参为:-----------------------" + 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层 刷新公共授权码的方法   的入参为: 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上,如果刚好能帮到你,记得给个赞鸭!

你可能感兴趣的:(微信开放平台第三方接入授权开发)