上图出自微信官方文档。流程图中的关键是“自定义登录态与 openid、session_key 关联”,意思是生成一个自定义登录态,且使之和 openid、session_key 关联起来。
所谓生成自定义登录态,其实就是生成一个具备时效性的字符串(通常把这个字符串称为 token),所谓关联 openid、session_key,就是根据这个字符串可以获得与之对应的 openid、session_key。
根据流程图,后端需要把 token 发送给小程序客户端,客户端会把它缓存在本地,客户端向服务器发起请求时携带上 token,服务器就能据此判断请求是否合法,以及请求由哪位用户发起。
现在的问题是,怎么生成 token?
第一步:前端用 wx.login()
获得 code
,然后发给后端,后端据此请求 code2Session,如果成功获得 openid、session_key,就使用下面的方案一或方案二生成 token,如果 code2Session
报错,就说明这个 code 可能是捏造的或已失效,此时需要拦截这个请求。
随机生成一个唯一的字符串,就可以用作 token 了,但返回之前,需要把它和 openid、session_key 关联保存到数据库中。前端向后端发起请求时携带这个 token,后端根据这个 token 查询数据库,如果可以获得 openid、session_key,就说明这个 token 是合法的,可以进一步返回业务数据,反之,则拒绝请求。
那如何为这个 token 增加时效性呢?为了保护后端接口,为 token 增加时效性是必须的,否则 token 一旦泄漏,接口就可能被持续滥用。对于暂时有效的 token,即使泄漏了也不会造成长时间的滥用。
在这个方案中,token 的时效性可以直接沿用 session_key 的时效性。根据文档,session_key 存在有效期,且有效期不向开发者公开,每一次 wx.login()
都会导致现有的 session_key 失效。
通过 wx.login 接口获得的用户登录态拥有一定的时效性。用户越久未使用小程序,用户登录态越有可能过期。反之如果用户一直在使用小程序,则用户登录态一直保持有效。具体时效逻辑由微信维护,对开发者透明。除了过期失效外,触发获取临时登录凭证 code 的操作(小程序登录 和 数据预拉取)可能会生成新的登录态 session_key,从而使旧的 session_key 被顶替而失效。
小程序端可以通过 wx.checkSession()
判断当前 session_key 是否过期,token 沿用 session_key 的有效期,意味着也可以使用 wx.checkSession()
判断 token 是否过期。
注意 wx.checkSession()
可靠性是不达 100% 的,但在这里不影响,除非你需要使用 session_key 解密用户数据。另外,这个接口有调用次数限制,但由于它的结果是在本生命周期一直有效,所以可以把它的结果保存为全局变量。
使用 JWT 作为 token。
首先使用 mkjwk.org 生成 Public Key 和 Private Key,操作方法可参考阿里云函数文档。
生成 jwt 需要使用 Public Key,可以在 jwt.io 在线测试一下如何生成 jwt。
对于 Node.js 可使用 jose 生成 jwt,生成有效期为 2h 的 jwt 的示例代码如下:
const alg = "RS256";
const pkcs8 = `-----BEGIN PRIVATE KEY-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END PRIVATE KEY-----`;
const privateKey = await jose.importPKCS8(pkcs8, alg);
const jwt = await new jose.SignJWT({ openid: "my-openid" })
//使用 mkjwk.org 生成 key 时可以设置的 kid,如果设置了,这里的 kid 就是当时设置的值
.setProtectedHeader({ alg, kid: "key-id" })
.setIssuedAt()
.setExpirationTime("2h")
.sign(privateKey);
把 jwt 返回给小程序客户端,客户端可以从 jwt 中提取 payload 并转换出明文信息,例如在上面的代码中,可以从 payload 提取 openid、jwt 创建时间、jwt 有效期三项信息。小程序端可以根据 payload 中的有效期自行判断 token 是否失效,失效了再重新请求获取新的 token。
或者,小程序端也可以在在 app.js 的 onLaunch
中总是获取新的 token,这样就无须关注之前那个 token 是否还有效,虽然 wx.login()
有次数限制,但这种情况下不可能超额。
小程序向服务器发送请求时携带上 jwt,服务器也可以用 jose 对 jwt 进行验证,如果通过验证,即说明这个请求合法,同时可获得 jwt 中的 openid。
两个方案的几个区别: