最近工作需要,运营想实现一个功能,让网站用户可以通过微信进行登录。这在社交媒体如火如荼的今天,再常见不过了。

提到第三方登录的技术范畴,就不得不提OAuth,要说这个OAuth,我就不啰嗦了,还是推荐大家直接看阮老师的理解OAuth 2.0,没错,这个协议已经是2.0版本了!

不是太复杂,对吧?了解了理论姿势,我们再来结合实际场景完成功能,首先要知道,我们的场景不是要自己实现这个OAuth协议,而是根据第三方平台(这里特指微信)来完成我们的需求。

一直没写网站类型的系统,所以一直也没怎么去和常用的第三方系统对接,懒嘛,时间多花在打电动上了……不过其实知道并不复杂,我们来先梳理一下要用到的技术:

  • ajax(看具体场景了,我所处理的系统需要)
  • json
  • OAuth2.0

大概就这些吧,前两个基本上是web2.0以后的必备知识,而第三个我们刚才也介绍过了,那么就开始动手吧!

哦~稍等一下,在写代码之前,我们还没看微信接口的规则呢,说到这个,我这个小白就得叮嘱一下大家了,微信平台分2个:公众号平台开放平台。前者多用来结合公众号后台来完成更加复杂的业务逻辑的,当然也能解决我们今天讨论的这个登录问题,但后者更适合我们!

顺便说一下,要使用微信的接口是要申请的,申请是要审核的,审核是要收费的!目前应该是申请一次300块,申请所需要的手续和申请支付宝差不多,就是一些备案号啊,公司执照啊等等吧,可以访问这里

目前微信开放平台针对网页提供的就只有用于第三方登录的接口(其实也就是获取用户信息的接口),相关接口文档可以访问这里

我大概描述一下我们的业务流程走向:

  1. 用户登录网站,并进行微信账号绑定;
  2. 点击绑定后浏览器跳转到微信鉴权页面,用户完成授权;
  3. 微信平台将用户浏览器重定向到我们指定的地址,该地址作用是将当前用户id和该用户微信的openid(或unionid)建立映射关系;
  4. 用户再次打算登录网站时,点击微信登录按钮;
  5. 浏览器跳转到微信二维码页面,用户扫描确认后,浏览器跳转到我们指定的页面;
  6. 该页面根据用户微信的openid查找到对应的网站用户id,并完成session的创建;
  7. 引导用户浏览器跳转到网站首页,此时用户应该处于登录状态。

该流程很简单,具体你的项目是使用什么技术栈,都可以根据以上流程完成微信同步登录。有童鞋可能注意到,我们并没有按照官方说的那样去持久化access_token,是的,因为目前我们并没有必要持久化它,更不需要为了避免超时而去续期,因为我们只是一次性获取用户的openid而已。

那什么时候我们需要保存access_token呢?当你的网站除了登录外,还想让更深度的绑定微信账号时才需要(例如支付,当然你可能需要开通微信公众号平台喽~)。

我的场景

由于我的网站是完全基于ajax的,前后台仅仅靠api来通信,所以和微信官方的例子还不完全一样,下面我来说一下这种场景下的注意细节。

请允许我先介绍一下我的场景:

  1. 富客户端,前台完全负责页面的路由
  2. 所有前后端通信按照Restful标准
  3. 会话id使用自定义请求头(token)来进行传递

满足以上条件的系统,通常是使用了像angularjs,emberjs或reactjs等前端mv*框架技术的。

出于各种原因,我们上面描述的流程中,有一些是被影响的。我们来具体说说吧~

首先看第2步(点击绑定后浏览器跳转到微信鉴权页面,用户完成授权),由于这次浏览器跳转会将控制权交给微信平台,我们的前台路由不能插手,所以呢,我们需要传递足够的参数过去。微信接口提供了一个参数state,刚好可以让我们来使用,因为该参数微信会原样返回给我们指定的回调页面,为什么这一点很重要呢?

第3步(微信平台将用户浏览器重定向到我们指定的地址,该地址作用是将当前用户id和该用户微信的openid(或unionid)建立映射关系)中,其实我们省略了一个子流程,根据OAuth规范,微信回调到我们的指定地址后,只传递了Code,而这个Code并不能用来换取用户的openid,我们还需要在该地址的处理逻辑中再次请求微信平台换取access_token,这次请求要携带一个appsecret参数,该参数不应当泄漏给其他人,所以注定我们的这个地址是一个不在客户浏览器中执行的脚本,也就是通俗的后台代码(可以是php,是java,是nodejs)。

所以呢,当我们的这个后台脚本顺利拿到了用户的openid后呢,它又该怎么知道用户的系统id呢?有人说很简单啊,直接读取cookie即可,我前面说了,我们没有使用cookie来存sessionid,我们使用的是自定义请求头,而微信回跳时肯定是不会携带我们自定义的请求头的,所以前面说的那个state参数就很有意义哒。

同样,我们持久化用户的系统id和openid是需要使用数据库的,你当然不会希望将数据库密码放在前台代码里吧?哇哈哈~~别告诉我你是这么做的!因此,微信回调的地址一定是后台脚本~

同理,第6步依然是一个后台脚本,它创建好会话id后,需要将用户的浏览器重定向到前台系统页面,相当于把控制权交还给了我们的前台系统。接下来就是在我们的富客户端路由控制下完成用户的页面路由了。

还有一个细节,用户绑定过微信账号后,你网站的某个页面肯定还要展示这种绑定关系,之所以提这点,是想让你知道,用户系统id和openid的关系映射方式还是需要斟酌一下的,因为我们需要两种方向的查找:

  • uid -> openid:用于绑定
  • openid -> uid:用于登录

如果你使用的是nosql(redis),你可能需要两组kv才能存下这两组数据。同时,你可能还需要考虑其他平台的账号绑定~~所以,你还是先好好考虑考虑吧~

实际开发时你还需要考虑到足够多的异常系处理,毕竟在整个流程中存在多次跳转,多次查询,要考虑每次的出错场景。不说了,说多了都是泪!

代码我已发到gihub上,有兴趣的童鞋不妨去看看~