基于NodeJs的微信第三方平台认证授权流程
为什么80%的码农都做不了架构师?>>>
需要解决的问题
微信的第三方开发者平台采用的是OAuth的认证流程。因此,需要解决的问题是:
按照OAuth的流程,调用微信第三方平台的api来获取pre_authenticatioin_code 等参数,提供授权页面让服务号管理者授权;
这里面,有7、8个参数需要通过微信API一步一步的获取。而且当中的某些参数是有过期时间的,因此,需要后端服务能够在过期前自动调用微信API重新获取,并保存这些参数;
考虑到要能够支持多家不同的维修服务号业务应用,因此需要schema设计上支持多个tenant;
需要考虑冷启动的问题。因为,这些参数缓存在memory里面(持久化存储是没有意义的,因为大部分参数过期失效),所以,需要考虑如何从DB 中load 部分参数(如何component access token)以及 快速重新发起微信参数请求;
这7、8个参数是有依赖关系的,用前一个获取后一个。因此,一旦网络连接失效,在获取前面的参数不能成功的情况下,需要重试,成功后,再接着下面的参数请求。
具体步骤
(因为这些代码还没有来得及放到 npm上,所以有需要代码的mail to fxfx_001@163.com. )
(一)获得微信开发者资质及部署公众号应用的步骤
这一步,网上已经有很多可以参考的资料。 无非就是去提供一个企业工商名称,提供一个银行卡号,然后花点钱,让微信给你开个账户。 之后,进入到微信第三方开发者平台,选择公众号第三方平台。
注释 : 只有处在白名单列表中的微信后台服务才能够接调用wechat 平台的api。因此,一般情况下,可以通过nginx的forward proxy 做后台微信的前置机,每次调用api的时候,传出的是这个nginx所在机器的ip地址。
另外,还需要注意的是,这里面所有涉及域名的部分最好保持一致。
(二)存储在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
基于NodeJs的微信第三方平台认证授权流程相关推荐
- 微信第三方平台之授权流程(三)
注:这篇文章基础是小程序已创建成功(不懂得看我前面的文章) 授权流程技术说明 小程序或者公众号授权给第三方平台的技术实现流程比较简单,如下图所示: 第三方服务商构建授权链接放置自己的网站,用户点击后, ...
- 微信第三方平台公众号授权流程1—第三方平台概述概述
一.概述 公众平台第三方平台是为了让公众号或小程序运营者,在面向垂直行业需求时,可以一键授权给第三方平台(并且可以同时授权给多家第三方),通过第三方平台来完成业务,开放给所有通过开发者资质认证后的开发 ...
- 微信第三方平台授权流程- java
1. 微信第三方平台的开发,第一步就是公众号的授权,授权成功后第三方凭条才能利用公众号的appid和token获得公众账号额信息,并代替公众账号完成一些功能. 2. 授权的流程,理论图 代码流程, 1 ...
- 用微信第三方平台授权小程序业务
如果本文对你有用,请爱心点个赞,提高排名,帮助更多的人.谢谢大家!❤ 如果解决不了,可以在文末进群交流. 本文章转自php中文网:http://www.php.cn/weixin-kaifa-4069 ...
- 微信第三方平台-授权流程经验分享
原文地址: www.jianshu.com/p/67836ffa9- 在做微信第三方平台开发的时候,虽然授权的技术实现流程比较简单,但是相对于一个key直接集成的一些其他的服务来说,还是有一些步骤,过 ...
- 微信第三方平台授权时域名问题
最近在处理微信第三方平台的问题,在授权的时候总是提示"请确认授权入口页所在域名,与授权后回调页所在域名相同,并且,此两者都必须与申请第三方平台时填写的授权发起页域名相同." 如提示 ...
- 微信公众号通过第三方平台完成授权
微信公众号通过第三方平台完成授权前,第三方平台与公众号绑定关系. 为什么要使用第三方平台来完成授权,公众号本身授权也可以,但是微信平台会认为你具有开发能力,免费提供给你的自定义菜单以及其他功能将不能再 ...
- 微信公众平台如何授权第三方平台,干货到!微信公众号怎样添加第三方平台及取消授权
微信公众号是我们经常使用的行业资讯平台,通过公众号我们可以更有效的传播我们的服务信息.提供更改的服务水平.效率.有时候我们需要授权第三方平台来进行操作会更加方便,那么如何在微信公众平台授权第三方平台呢 ...
- 关于微信第三方平台授权61004:access clientip is not registered requestIP
关于微信第三方平台授权61004:access clientip is not registered requestIP 这个问题让我难受了2个小时,最后总结下问题: IP白名单 授权的服务器一定要添 ...
最新文章
- MySQL设值自动修改时间
- Bootstrap3 表格-带边框的表格
- HTML字体小于12谷歌不兼容,Chrome谷歌浏览器下不支持css字体小于12px的解决办法【原创】...
- java事件编程_java基础 ---Swing事件编程
- mysql执行过程五步_简单五步教你搭建MySQL主从复制
- SuperSet连接Hive失败(客户端报日志拒绝连接)
- XML/YAML文件的输入输出
- OO4O的session残留问题
- (二)Graphivz 简单结构图及子图
- [转载] Python导出Excel图表
- python2.7安装教程win7_win7下python2.7安装 pip,setuptools的正确方法
- 一次全链路压测-总结
- 计算机组装故障排除方法,计算机的硬件组装及故障排除
- c语言设计一个酒店管理系统,C语言酒店管理系统设计.docx
- vs2019新手怎么解决命名空间“”中不存在类型或命名空间名“”(是否缺少程序集引用?)问题全解
- 智安网络丨德勤发布2021九大技术趋势,零信任安全成为主流
- java 分层处理解耦_后端分层架构如何解耦?
- 自控力读书笔记 第八章	 传染:为什么意志力会传染?
- 如何将SNS光纤交换机(OEM博科FC交换机)恢复为出厂设置
- node14 升级 node16 后 vue2 项目中 sass 报错问题