很久很久以前,第三方授权登录就开始流行。比如 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]);
最后
网页示例这个稍微复杂一点。 后台页面一个域,请求的接口是另外一个域,小程序授权的页面又是另外一个域。也就是有三个域。