博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于NodeJs的微信第三方平台认证授权流程
阅读量:6480 次
发布时间:2019-06-23

本文共 8909 字,大约阅读时间需要 29 分钟。

  hot3.png

需要解决的问题

微信的第三方开发者平台采用的是OAuth的认证流程。因此,需要解决的问题是:

  1. 按照OAuth的流程,调用微信第三方平台的api来获取pre_authenticatioin_code 等参数,提供授权页面让服务号管理者授权;

  2. 这里面,有7、8个参数需要通过微信API一步一步的获取。而且当中的某些参数是有过期时间的,因此,需要后端服务能够在过期前自动调用微信API重新获取,并保存这些参数;

  3. 考虑到要能够支持多家不同的维修服务号业务应用,因此需要schema设计上支持多个tenant;

  4. 需要考虑冷启动的问题。因为,这些参数缓存在memory里面(持久化存储是没有意义的,因为大部分参数过期失效),所以,需要考虑如何从DB 中load 部分参数(如何component access token)以及 快速重新发起微信参数请求;

  5. 这7、8个参数是有依赖关系的,用前一个获取后一个。因此,一旦网络连接失效,在获取前面的参数不能成功的情况下,需要重试,成功后,再接着下面的参数请求。

具体步骤

(因为这些代码还没有来得及放到 npm上,所以有需要代码的mail to fxfx_001@163.com. )

(一)得微信开资质及部署公众号用的步

这一步,网上已经有很多可以参考的资料。 无非就是去提供一个企业工商名称,提供一个银行卡号,然后花点钱,让微信给你开个账户。 之后,进入到微信第三方开发者平台,选择公众号第三方平台。 

202800_2xvy_195358.jpg

191138_Fq4x_195358.jpg

注释 : 只有处在白名单列表中的微信后台服务才能够接调用wechat 平台的api。因此,一般情况下,可以通过nginx的forward proxy 做后台微信的前置机,每次调用api的时候,传出的是这个nginx所在机器的ip地址。

另外,还需要注意的是,这里面所有涉及域名的部分最好保持一致。

191139_mxKV_195358.jpg

(二)存储在wechat 通的数据schema

这个schema 中定义了wechat 通需要存储的服务号具体配置信息。 

var wxaccountSchema = new Schema({

    mdt_app_id:{

type: String, unique:true},

    wx_authorizer_apps : [{

        wx_authorizer_app_id:{

type:String,required:true},

        wx_authorizer_refresh_token:{

type:String,required:true},

        wx_origin_menu_config: {

type:String},

        wx_mapped_menu_config: {

type:String},

        wx_mapped_menu_click : {

type:String},

        wx_authorizer_info:{

type:String}

    }]

 

});

mdt_app_id - 是指一个门店通应用的app_id ,这个app_id 是在应用管理平台上创建时,系统分配给这个品牌商企业的一个唯一标识;

一个app_id 其实可以指定多个应用服务号 (当然,还有一个大前提是,门店通应用需要在wechat 第三方开发者平台通过全网测试)

wx_authorizer_app_id - 是指授权给门店通(wechat 通)管理的服务号应用的id ,这个id wechat 平台为每个服务号应用指定的;

wx_authorizer_refresh_token - wechat 通第三方授权的流程最终目的就是要获得这个 refresh_token 以及 访问/替代服务号接受消息的access_token。这里的两个token 一个是存储在db 中(因为,只有通过这个token 才能获得 access-token) , 一个是存储在redis 缓存中;

wx_*_menu_config - 等参数是用于当wechat 通接管微信服务号时,获取现有服务号中的menu 配置信息,为了不造成对终端用户的影响,当你被授权接管服务号时(具体是因为,wechat 通需要获取消息管理权限,而这个权限间接需要自定义menu的权限),需要能够重新设定菜单的配置,这些配置需要存储下来;

wx-authorizer_info - 是一个string 类型,这里存储的是wechat 服务号授予的具体权限集等信息。

(三)wechat 通中各个token及授权码的自动刷新获取

每个微信服务号第三方应用在创建时需要获取 verify-ticket 以及 component_access_token 以及pre_auth_code 这三个参数的获取顺序依次递进的,而且,每个code 都有过期时间,因此wechat 通需要make sure 这些参数在它过期前重新刷新并替换掉现有的。

而对于,wechat 通自己的应用配置信息,可以存储到db 中或者在应用启动时pass in 到配置文件里, 

wechat_platform_config : {

    appid: 'wx4ce1df4ef19c19ac',

    appsecret: 'ee57d830c7885b90eaba9be18b30f9fa',

    msg_token: '123456789mdt',

    

    msg_aeskey: 'p0o9i8u7y6t5r4e3w2q1azsxdcfvgbhnjmkl0987654',

    oauth_callback_url: 'http://wechat.mdt.goelia.com.cn/callback'

},

注: msg_token 及aeskey 等参数用于在收发wechat 平台的加解密信息时用于签名的验证。

1) 在wechat 通启动时,尝试获取verify-ticket 等参数 (verify-ticket ,每隔10分钟,wechat 平台会自动同步给应用端,获取后,再去获取component_access_token 及 pre_auth_code ,也可以看出这几个参数间的获取顺序)

function loadWxPlatformParams(){

    logger.info('Load mdt-wechat platform params');

    searchDependedParam(PLATFORM_TICKET_KEY)

        .then(function(){

            return redisClient.getAsync(PLATFORM_TICKET_KEY)

        })

        .then(function(param){

            return wxplatform.getComponentAccessToken(param);

        })

        .then(function(rs){

            return wxplatform.createPreAutCode(rs);

        })

        .then(function(){

            return wxservice.findWxAccountsBymdtAppId('')

        })

        .then(function(rs){

            if (rs instanceof Array && rs.length == 1){

                rs[0].wx_authorizer_apps.forEach(function(o){

                    redisClient.getAsync(PLATFORM_TOKEN_KEY)

                        .then(function(token){

                            return wxplatform.refreshAuthorizerAccessToken(token, o.wx_authorizer_app_id, o.wx_authorizer_refresh_token);

                        })

                        .then(function(rs){

                            logger.info('After refresh AuthorizerAccessToken, returned authorizer_app_id is '+rs+'. Going to set WechatApi Instance');

                            //set wechat api instance

                            return wxplatform.setWechatApiInstance(rs);

2)存储在redis 中各个参数被设置了expire 时间,当expire 时间到了的时候,wechat 通会获取到对应的事件来触发重新刷新的过程

redisPubSub.on(REDIS_EXPIRE_EVENT, function(data, channel){

    if (PLATFORM_TOKEN_KEY_SHADOW === data){

        searchDependedParam(PLATFORM_TICKET_KEY).then(

            function(param){

                return wxplatform.getComponentAccessToken(param);

            }

        ).then(function(rs){

            logger.info('Trying to refresh component token - '+rs);

        }).catch(function(err){

                //retry....

        });

 

    } else if (PLATFORM_PRE_AUTH_CODE_KEY_SHADOW === data){

        searchDependedParam(PLATFORM_TOKEN_KEY).then(function(param){

            return wxplatform.createPreAutCode(param);

        }).then(function(rs){

            logger.info('Trying to refresh '+PLATFORM_PRE_AUTH_CODE_KEY+' - '+rs);

        }).catch(function(err){

            //retry....

        });

    } else if (_.endsWith(data,'authorizerAccessToken-shadow')){

        var ds = _.split(data,':');

        var authorizer_app_id = ds[1];

 

        //TODO - PLATFORM_AUTHORIZER_ACCESS_REFRESH_TOKEN & PLATFORM_AUTHORIZER_APPID should be tried reading from DB firstly.

        //TODO - If not there, check if the authorization_code expires. If not, go getting it. Otherwise, ask customer to re-do OAuth.

        Q.all([searchDependedParam(PLATFORM_TOKEN_KEY),wxservice.findRefreshTokenByAuthAppId('',authorizer_app_id)])

            .spread(function(token,refresh_token){

               if (token && refresh_token && authorizer_app_id){

                   return wxplatform.refreshAuthorizerAccessToken(token,authorizer_app_id,refresh_token);

               } else {

                   logger.info('refresh_token is '+refresh_token);

                   if (!refresh_token){

                       logger.info('refresh_token for authorizer_app_id '+authorizer_app_id+' is null!!');

                   }

               }

        }).then(function(rs){

            logger.info('Trying to refresh Authorizer Access Token - '+rs);

            return wxplatform.setWechatApiInstance(rs);

 

        }).catch(function(err){

            //retry....

            logger.error(data+' is expired from redis. But error happened during refreshing authorizer access token.'+ u.inspect(err,false,null));

        });

    }

});

3)  wechat 通生成让服务号管理员登陆授权的页面。其中的点击登陆授权link 是wechat 平台要求提供的。

其中可以看见,redirect_url 是wechat 通自己制定的auth_call_back url 链接。

exports.getOAuthPage = function(req, res) {

    //Obtain the pre_code only exists

    redisClient.getAsync(PLATFORM_PRE_AUTH_CODE_KEY)

        .then(function(value) {

            if (value) {

                var strUrl = 'https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=' + config.wechat_platform_config.appid + '&pre_auth_code=' + value + '&redirect_uri=' + encodeURIComponent(config.wechat_platform_config.oauth_callback_url);

                //var header = '';

 

                //var body = '<a href=\"' + url+ '\" id=\"authurl\" style=\"display: inline;\">' + 'click here to authorize!!!</a>';

 

                //var html = '<!DOCTYPE html>'

                //    + '<html><header>' + header + '</header><body>' + body + '</body></html>';

 

                res.render('oauth', { oauth_url: strUrl });

            } else {

                res.status(404).end();

            }

        });

};

 

3) 服务号授权后,wechat 通需要保存各项menu config 以及获取access_token 以及refresh-token.

注: 这里的authCallback 是wechat 开放平台的OAuth2.0 协议的一个实现,即,你传入一个auth_callback 的url, 一旦服务号的管理员授权后,wechat 将authorization code 通过这个call back url 回传给wechat 通。 wechat 通,接下来可以用这个authorization-code 接着获取access_token.

exports.authCallback = function(req, res) {

    var authorization_code = req.query.auth_code;

    var expire = req.query.expires_in;

 

    logger.debug('Authorization code is ' + authorization_code);

    //redisClient.set(PLATFORM_AUTH_CODE, authorization_code);

    //If the auth_code expires, it can only be obtained by asking cutomer re-do oauth.

    //redisClient.expire(PLATFORM_AUTH_CODE,expire);

    res.status(200).send("success");

    //Authorization code will expire. So, it is possible to get the access_token & refresh_token asap.

    //And by wechat specification, authorization code can only be obtained when doing the OAuth.

    //So before expire, the authorizer_access_token can be obtained for multi-times.

    _getAuthorizerAccessToken(authorization_code)

        .then(function(rs) {

            //TODO - Set the mdtAppId formally

            //set wx_app_id and wx_refresh_token to database.

            return wxservice.createWxAccount('', { "wx_authorizer_app_id": rs.authorizer_appid, "wx_authorizer_refresh_token": rs.authorizer_refresh_token });

        })

        .then(function(rs) {

            //Note- Then save it to redis as well.

            redisClient.set(u.format(PLATFORM_AUTHORIZER_ACCESS_REFRESH_TOKEN, rs.wx_authorizer_app_id), rs.wx_authorizer_refresh_token);

            return _obtainWxAccountDetailInfoAndSave(rs.wx_authorizer_app_id);

 

        }).then(function(rs) {

 

            return _setWechatApiInstance(rs.authorizer_app_id);

        }).then(function(rs) {

            //generate menu config

            return _convertMenus(rs.authorizerAppId);

        }).then(function(rs) {

 

            //save to db for menu config

            //Hardcode the mdtAppId

            return wxservice.updateWxAccountMenuConfig('', rs);

        }).then(function(rs) {

 

            //create menu

            return _createWxAccountMenu(rs);

4)获取refresh_token 及access-token 后创建应用(app_id)对于的wechat api instance。 这个instance主要用于操作服务号中的各种api (如,模板消息api,客服接口消息api,素材管理api 等)

function _setWechatApiInstance(authorizer_app_id) {

    var deferred = Q.defer();

    redisClient.getAsync(u.format(PLATFORM_AUTHORIZER_ACCESS_TOKEN, authorizer_app_id))

        .then(function(authorizer_access_token) {

            if (authorizer_app_id && authorizer_access_token) {

                var api = new WechatAPI(authorizer_app_id, "", function(callback) {

                    //Make sure the token is refreshed before the expire time. So, for wechat-api, just set the expire time for a

                    //far time

                    var ts = new Date('2016-12-30 23:59').getTime();

                    callback(null, { "accessToken": authorizer_access_token, "expireTime": ts });

                });

                apiWechatMap.set(authorizer_app_id, api);

                deferred.resolve({ 'authorizerAppId': authorizer_app_id });

            } else {

                deferred.reject(new Error('No token value'), {});

            }

        });

 

    return deferred.promise;

转载于:https://my.oschina.net/geofx/blog/664054

你可能感兴趣的文章
原生Js交互之DSBridge
查看>>
Matlab编程之——卷积神经网络CNN代码解析
查看>>
三篇文章了解 TiDB 技术内幕 —— 说计算
查看>>
copy strong weak assign的区别
查看>>
OpenCV 入门
查看>>
css 3D transform变换
查看>>
ele表格合并行之后的selection选中
查看>>
正则表达式分解剖析(一文悟透正则表达式)
查看>>
解决UILable标点符号居中的问题
查看>>
HTML5新特性教程
查看>>
ImageOptim-无损图片压缩Mac版
查看>>
12 Go语言map底层浅析
查看>>
vue-resumer 项目中 element-ui 遇到的 textarea autosize 问题
查看>>
以主干开发作为持续交付的基础
查看>>
PHP扩展库PEAR被攻击,近半年下载者或被影响
查看>>
传统运维团队转型应该注意哪些问题?
查看>>
JavaScript函数(二)
查看>>
Airbnb改进部署管道安全性,规范部署顺序
查看>>
腾讯最大规模裁撤中层干部,让贤年轻人
查看>>
当我们谈性能的时候,我们实际上在谈什么?
查看>>