获取用户信息与小程序登录是息息相关的。所以先讨论小程序登录。
小程序登录
小程序登录的具体流程,在官方文档中描述的非常清楚。这里主要阐述一下对code和session_key的理解。
从流程图中可以发现,整个流程的第一步是在微信小程序中通过wx.login()方法获取code,然后将该code传给后端,后端拿着这个code调用auth.code2Session接口获取session_key和openid。
code使用一次之后就会失效,而对于session_key的作用,文档也给出了很好的说明:
会话密钥session_key是对用户数据进行加密签名的密钥。为了应用自身的数据安全,开发者服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。
从这句话可以看出,由code转换为session_key和openid是为了安全性考虑(真是一句废话),打个比方呢,就像初始密码与修改密码差不多的道理吧。
另外可知,对于开发者来说,session_key主要是为了解密用的,那么要对什么进行解密?又要如何解密呢?这里通过获取手机号这一业务场景进行说明。
获取手机号
在小程序中获取手机号的具体步骤,在官方文档文档中也有很好的描述(也许我就是懒得写吧)。这里主要说一下session_key的处理以及加密与解密的过程。
在小程序中,通过将button组件open-type的值设置为getPhoneNumber后,获得的手机号信息并不是13811111111这样的明文,而是两个加密之后的字段:
| 参数 | 类型 | 说明 |
|---|---|---|
| encryptedData | String | 包括敏感数据在内的完整用户信息的加密数据 |
| iv | String | 加密算法的初始向量 |
具体加密解密流程
小程序开发者:腾讯大爷,用户已经授权给我手机号啦,现在可以发我吗?
腾讯:等会,我用session_key加密一下,好啦,给你
小程序开发者:我靠,这一堆乱码,后端大哥,帮解密一下呗
后端大哥:解密可以,但是我这里没有session_key,给我个code。
小程序开发者:好嘞,wx.login(),走你。
后端大哥:调用auth.code2Session接口,耶!session_key到手,现在可以给我encryptedData和iv了。
小程序开发者:嗯,再走你。
balabala...
后端大哥:解密好了,手机号13811111111。
重点讨论
整个过程最重要的是session_key获取的时机,因为如果微信加密的session_key与后端解密的session_key不是同一个的话,是无法解密的,在官方文档中也有很好的说明:
在回调中调用wx.login登录,可能会刷新登录态。此时服务器使用code换取的 sessionKey不是加密时使用的sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用checkSession进行登录态检查,避免login刷新登录态。
可见,有两种处理方式,按照时间发生的时序,描述如下:
方式一:
- 小程序调用
wx.login()获取code并发起请求A,将code传到后端; - 后端获取
session_key,并对请求A做出响应; - 小程序收到请求A的响应后,才能允许用户点击获取手机号的
button(可在发起请求的过程中,一直loading,防止用户点击); - 用户点击
button,获取加密的手机号信息; - 调用解密手机号的接口。
方式二:
- 用户点击
button获取加密的手机号信息; - 在
bindgetphonenumber的回调函数中,调用wx.checkSession()方法,检测当前用户登录态是否有效。- 如果有效(
success回调函数执行),则说明后端之前已经获取过session_key,并且是有效的。直接调用解密手机号的接口。 - 如果无效(
fail回调函数执行),则需要先调用wx.login()获取code并发起请求B,后端获取有效的session_key后,响应请求B,然后小程序在请求B的回调中调用解密手机号的接口。
- 如果有效(
而上方的具体加密解密流程,描述的就是方式二的处理过程。
当然,同时结合这两种方式一起使用,是最保险的方式啦。
最后贴一下方式二的代码:
<button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">Click Me</button>>
function getPhoneNumber(e) {
const {encryptedData, iv} = e.target;
wx.checkSession({
success: () => {
// 调用解密手机号的接口
decodePhoneNumber({encryptedData, iv});
},
fail: () => {
wx.login({
success: (res)=>{
// 更新session_key的接口
getSession(res.code).then(() => {
// 调用解密手机号的接口
decodePhoneNumber({encryptedData, iv});
});
}
});
}
})
}
以上。End。