在微信公众帐号中进行OAuth授权登陆
微信内收藏文章
假设有微信用户A君,订阅了公众帐号“小道消息”(微信号DbaNotes),每天收到一篇文章推送。A君有时希望能把文章收藏起来,可以稍后阅读或者反复欣赏。当然,可以有千百种方法来完成收藏这个事,A君是个微信脑残粉,希望整个收藏过程仅在微信内即可完成。
印象笔记的微信公众号“我的印象笔记”是基于微信的私有API开发的,可以把消息、文章的转存为笔记。使用该帐号的基本的步骤是:关注“我的印象笔记”,其会提示用户绑定印象笔记的帐户,并发送OAuth授权登陆链接,点击链接到印象笔记网站登陆,完成后,给“我的印象笔记”发送的内容会保存为笔记。使用微信内置Web页面打开文章后,点击分享按钮,“我的印象笔记”已经内置其中,可以一键保存文章。总体上,“我的印象笔记”是很方便的。如果A君是印象笔记的用户(注意,Evernote的国际帐号是无法使用的),那么A君有福了。可惜A君是Evernote用户,而且也不太习惯在Evernote内阅读文章。
A君是Pocket的忠实粉丝,平时在网上闲逛,遇到有意思的文章,会保存到Pocket阅读列表,在PC上这个很简单。A君希望能有一个类似“我的印象笔记”的公众帐号,绑定自己的Pocket帐户,通过发送文章链接的消息给该帐号,将文章添加至Pocket列表。
我们假设这个公众帐号叫MyPocket,下面重点叙述,如何将用户的Pocket帐户OAuth授权给MyPocket。
节点
在整个的授权登陆过程中,涉及到的节点主要有下面四个:
节点1:微信用户
。例如A君。
节点2:公众号消息服务器
。用户可见的是公众帐号,例如MyPocket。
节点3:微信服务器
。微信官方服务器,完成1和2之间的消息传递。
节点4:Pocket授权登陆服务器
,Pocket应用自身的登陆服务器或页面。
文本消息传递步骤
微信用户节点1发送一条文本消息给公众号节点2,并得到公众帐号的回应,消息传递的过程如下:
(1),微信用户节点1发送消息
(2),消息到达节点3,对消息进行封装通过HTTP POST推送给节点2
(3),消息到达节点2,处理后对HTTP进行响应,返回给节点3,转发给节点1
(4),消息到达节点1,微信用户接收到消息回应
注意,目前节点2是无法主动发送消息给用户的,仅能在收到节点3的消息推送后,进行消息回复。
OAuth授权登陆步骤
和普通的在浏览器中完成的OAuth授权登陆有所不同,在微信中OAuth登陆的基本步骤是:
1,微信用户节点1发送特定表识消息(例如“auth”)开始OAuth授权登陆
2,节点2收到消息后:
(1)记住节点1的标识串FromUserName,需要保存
(2)到节点4获取request token,需要保存
(3)给节点1消息回复:用户授权登陆的地址链接,地址链接中需要包含:用户表识串FromUserName和授权完成后的回调(callback)地址
3,节点1打开登陆链接,微信内置浏览器中填写帐户,进行授权
4,节点4重定向至节点2的callback地址
5,节点2处理授权登陆callback,根据request token获取access token,授权完成,需要保存access token。
下面是对该过程的几点说明:
(1)消息推送中所携带的用户相关信息只有FromUserName,是一个加密后的字符串,形如oGfS0jrOSOwJXqNTIy9B8WI8sEac。因此直接获取用户的微信号或者昵称存在困难。据从网上查询到的信息看,一个微信用户发送给某特定的公众帐号的所有消息,该FromUserName是固定的。可用来唯一标识节点1。
(2)步骤2中的FromUserName、request token和步骤5的access token都需要保存。因为节点2会和节点3,以及节点1的浏览器发生HTTP交互,无法使用浏览器cookie,此处使用数据库进行保存操作。
(3)浏览器支持。因为OAuth登陆,其中一个步骤要到第三方的登陆授权界面进行登陆,需要用到浏览器,微信内置的浏览器,可以完美完成此任务。
代码
本例采用python的flask框架实现了一个小例子,主要的代码整理如下。授权登陆的步骤2代码:
@app.route('/weixin', methods=['POST']) def weixin_msg(): data = request.data msg = parse_msg(data) content = msg['Content'] username = msg['FromUserName'] if content == 'auth': pocket = Pocket(POCKET_CONSUMER_KEY, BASE_URL + url_for('auth_callback') + '?username=' + username) code = pocket.get_request_token() #code即为request token url = pocket.get_authorize_url(code) #保存code和username user = User(username) user.pocket_code = code db.session.add(user) db.session.commit() rmsg = u'点击下面链接登陆Pocket并授权给给我\n\n' + rmsg.encode('utf-8') return response_text_msg(msg, rmsg) return help_msg()
步骤2返回的认证地址Authorize Url,是通过微信消息回复给节点1的,由节点1手动点击链接打开,此处无法使用cookie,本例采用数据库保存session数据。其中BASE_URL + url_for('auth_callback') + '?username=' + username为重定向地址,节点4确认授权以后,重定向至节点2的,新增了"username"参数,可以理解为session id。
步骤5代码:
@app.route('/auth_callback') def auth_callback(): username = request.args.get('username') user = User.query.filter_by(username=username).first() pocket = Pocket(POCKET_CONSUMER_KEY) resp = pocket.get_access_token(user.pocket_code) user.pocket_token = resp['access_token'] user.pocket_username = resp['username'] db.session.commit() return '<html><head></head><body><h1>认证成功,发送文章链接给MyPocket,即可保存到Pocket阅读列表。</h1></body></html>'
授权完成后,用户直接发送文章链接,节点2匹配到Url地址,即添加到Pocket列表:
def add_item_to_pocket(username, content): r = r"(http://[^ ]+)" urls = re.findall(r, content) rmsg = u'' user = User.query.filter_by(username=username).first() pocket = Pocket(POCKET_CONSUMER_KEY) pocket.set_access_token(user.pocket_token) for url in urls: itemjson = pocket.add(url=url) item = json.dumps(itemjson) rmsg += u'已添加"%s"至Pocket\n' % item['title'] return rmsg
用法
A君想要收藏小道君发送的文章“「大概8点20分发」”,在微信中点击推送消息,内置浏览器打开文章,点击右上角的分享按钮,点选“复制链接”,打开MyPocket,粘贴,发送的链接如下:
http://mp.weixin.qq.com/mp/appmsg/show?__biz=MjM5ODIyMTE0MA==&appmsgid=10000250&itemidx=1#wechat_redirect
这样,A君就可以在3月17日的早晨大概8点20分在Pocket中欣赏这篇好文了。你不妨也试试看:
资源
1,Pocket API的python sdk。