Write the Code. Change the World.

10月 13

很久很久以前,第三方授权登录就开始流行。比如 qq、微博、github、微信这些。如果有一个大的平台来做这个服务的确是很方便的一个事情。在这些授权后都会有一个回调页面,就是从自己的网页跳转到授权页面,再跳转回来。但是,小程序是没提供这个服务的。但是自己可以构造类似的方式。
整个环节,代码都是由自己控制,可以很灵活的视线需求。仅仅是生成小程序码和扫码登录上小程序是腾讯那边做的。

示例:https://www.zeipan.com/admin

步骤

关键点:这里以 jwt 的认证方式来实现授权登录。

  • 登录页获取并展示生成携带参数的小程序码
  • 登录页面轮询获取登录状态(这里是 token)
  • 手机微信扫描小程序码并授权同意登录

后来说明

可以使用 EventSource 来代替轮询或 websocket 来实现该场景的处理。
https://blog.vini123.com/1062

细节说明

前后端分离的项目,jwt 的认证方式被普遍应用。服务端生成能代表用户身份的 token 给到前端,前端每次都会带上这个 token 去请求接口。token 正确,后端就能拿到当前请求的用户的信息。

token 可以定义有效时间和可刷新时间,通常可刷新时间是大于有效时间的。

说到底,无论是密码登录还是验证码登录还是其他啥登录,只要能生成正确的 token 给到前端就行。那么怎么做到正确呢。总不能把 A 用户对应的 token 给到 B 用户。这里借助小程序的扫码来卡这个点,来卡这个身份。

手机扫码后,会打开对应的授权页面。此刻用户的状态可能是已登录小程序,也有可能就是第一次扫码进来,根本就没登录过。没登录过的必须先登录。所以,在这里先得依靠小程序项目自身的登录逻辑登录进来。

直到用户登录好了小程序,点击授权登录。这个时候,会通过接口去请求服务端的授权登录。因为小程序是登录态,知道当前请求的用户信息,所以也能生成对应用户的 token 了。 然后把这个生成的 token 给到网页端轮询的那个接口。不就可以登录成功了吗。

在上边说的这几个环节里边,有个很重要的东西没说。就是生成的 token 怎么给到那个在轮询的页面。想象一下画面,此时可能有成百上千个不同电脑浏览器在请求轮询登录。生成的 token 该给谁呢。其实,在最开始就讲了一下子。就是代携带参数的小程序码。

用户打开浏览器,打开登录页面。就会向服务端发起一个获取小程序码的请求。生成小程序码的参数仅仅需要一个唯一识别就好。可以用 uuid,也可以用 session_id。反正只要是全局唯一的就可以。这里使用的是 session_id。

$key = session_create_id();

一定要确保唯一性。当前授权整个环节中的唯一性,通常 1 分钟内能授权完成。

生成小程序码的参数如下。

        $key = session_create_id();
        $data = [
            'scene' => 'key=' . $key,
            'page' => 'pages/mine/part/admin-login',
            'width' => 300
        ];

其实 page 对应的是扫码后,打开的小程序页面。scene 中是传递的参数。记住该参数总长度不能超过 32 个字符。稍微不小心就超了。width 对应的是小程序码的宽度高度。

这个 key 就是在整个环节中的桥梁。生成小程序码的时候,通过 redis 等方式将 key 存起来,就是以 key 作为 key,存一个空值。当小程序授权调用时,调用方会携带这个 key。所以可以将生成的 token 赋值给到这个 key 上。于是轮询的时候,查到这个 key 有值,就表示登录成功了。

接口

接口一: 生成小程序码

admin/api/qrcode

# response
# 小程序码的 base64 地址
# key
# key 的有效期

接口二:轮询

admin/api/token

# request
# key

# response
# token(直到生成或无效)

接口三:授权登录

api/admin-login

# request
# key

# respone
# result (授权成功或失败[逻辑失败、过期了])

其实,就这三个接口。第一第二个接口是网页端调用的。第三个接口是小程序端调用的。 request 是请求时,需要携带的参数。 response 是请求后返回过来的数据

性能

  • key 的有效期可以定义为 2 分钟。这个是考虑到陌生用户的。他需要扫码,然后去登录小程序,然后再授权。如果对于已登录的用户,几秒钟就可以操作完成
  • key 过了有效期,不再进行轮询。前端会出一个半透明底色盖住小程序码,在底色上有一个刷新二维码的按钮。只有点击按钮,请求或去新的小程序码后才又进行轮询。
  • 轮询的间隔。这里定义为 3 秒请求一次。也可以更大一点或更小一点
  • 如果不用轮询可以使用 websocket 来实现。如果仅仅是一个登录,单独用 websocket 来处理有点浪费

有效期 redis 可以这么搞。

$key = session_create_id();

if (Redis::exists($key)) {
    return response()->json(['message' => '请刷新'], 403);
}
Redis::setex($key, 120, null);

session id 测试上,是不会有重复的。

代码

生成带特定参数的小程序码

        $key = session_create_id();

        if (Redis::exists($key)) {
            return response()->json(['message' => '请刷新'], 403);
        }

        Redis::setex($key, 120, null);

        $data = [
            'scene' => 'key=' . $key,
            'page' => 'pages/mine/part/admin-login',
            'width' => 300
        ];

        // 获取或生成 access token
         $access_token = 'xxx'
         $url = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=' . $access_token;

        $result = curl($url, json_encode($data), true, true);

        // 这里省略了一些判断逻辑
        $image = "data:image/jpeg;base64," . base64_encode($result);

        return response()->json(['path' => $image, 'key' => $key, 'ttl' => 120]);

最后

网页示例这个稍微复杂一点。 后台页面一个域,请求的接口是另外一个域,小程序授权的页面又是另外一个域。也就是有三个域。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注