小程序登录——用户个人信息解密
前段时间在忙一个微信小程序的项目,这边就用户登录和用户信息的解密来做一个总结。
一、小程序登录流程

- 小程序向
wx.login()
发送请求获取到 code
- 小程序将自己的登录信息和
code
发送到开发者的服务器
- 服务器拿到
code
和申请到小程序申请的AppID
和AppSecret
一起发送到微信后台
- 微信后台返回
session_id
和openid
给开发者服务器
- 服务器通过特定算法校验签名解密信息
- 服务器给小程序下发令牌
token
- 小程序将
token
存入内存并加在每次请求的请求头中
- 服务器验证
token
的合法性,并返回业务数据
二、小程序 code 的获取
1. wx.getUserInfo()
该接口会返回用户信息和用户的明文信息编码

encryptedData
:包括敏感数据在内的完整用户信息的加密数据
iv
:加密算法的初始向量
signature
:使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息
userInfo
:用户信息对象,不包含 openid 等敏感信息
errMsg
:返回状态信息
2. wx.wx.login
调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户的唯一标识(openid)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。

errMsg
:返回状态信息
code
:用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 auth.code2Session,使用 code 换取 openid 和 session_key 等信息
三、搭建服务器接受参数
小程序端发送参数
1 2 3 4 5 6 7 8 9 10 11 12 13
| uni.request({ url: 'http://127.0.0.1:8080', method: 'POST', data: { js_code: this.js_code, encryptedData: this.encryptedData, iv: this.iv, rawData: this.rawData, signature: this.signature }, success: function success(res) { console.log(res.data); _this.text = 'request success'; } });
|
后端参数接收
在这里我们以 Springboot 为例作为我们的开发者服务器后来来接受传递参数,这里只是简单测试解密就将业务代码放在 controller 层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @RestController @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
@CrossOrigin @PostMapping("/") String index(@RequestBody Map map) { String code = (String) map.get("js_code"); String encryptedData = (String) map.get("encryptedData"); String iv = (String) map.get("iv"); String rawData = (String) map.get("rawData"); String signature = (String) map.get("signature"); System.out.println("code: "+code); System.out.println("encryptedData: "+encryptedData); System.out.println("iv: "+iv); System.out.println("rawData: "+rawData); System.out.println("signature: "+signature); return "ok"; } }
|
获取AppId
和AppSecret
在解密之前我们还需要去微信平台申请我们的小程序的一些 特征id https://mp.weixin.qq.com/

四、密钥请求
这里我们需要访问微信后台的结合来拿到解密密钥 auth.code2Session
请求地址
1
| GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
|
请求参数
属性 |
类型 |
默认值 |
必填 |
说明 |
appid |
string |
|
是 |
小程序 appId |
secret |
string |
|
是 |
小程序 appSecret |
js_code |
string |
|
是 |
登录时获取的 code |
grant_type |
string |
|
是 |
授权类型,此处只需填写 authorization_code |
返回值
返回的 JSON 数据包
属性 |
类型 |
说明 |
openid |
string |
用户唯一标识 |
session_key |
string |
会话密钥 |
unionid |
string |
用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回。 |
errcode |
number |
错误码 |
errmsg |
string |
错误信息 |
发送请求
1 2 3 4 5 6 7 8 9 10 11 12
| String AppSecret = "你的AppSecret"; String AppID = "你的AppID"; String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+AppID+"&secret="+AppSecret+"&js_code="+code+"&grant_type=authorization_code"; RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.getForObject(url, String.class); HashMap wx_key = JSON.parseObject(response, HashMap.class);
String session_key = (String) wx_key.get("session_key"); String openid = (String) wx_key.get("openid"); System.out.println("session_key: "+session_key); System.out.println("openid: "+openid);
|
fastjson 工具包引入 maven依赖引入
1 2 3 4 5
| ><dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.37</version> ></dependency>
|
五、算法解密
签名校验
1 2 3 4 5 6
| String signature2 = DigestUtils.sha1Hex(rawData + session_key);
System.out.println("signature: "+signature);
System.out.println("signature2: "+signature2);
|
maven依赖引入
1 2 3 4 5
| ><dependency> <groupId>org.apache.directory.studio</groupId> <artifactId>org.apache.commons.codec</artifactId> <version>1.8</version> ></dependency>
|
算法解密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| JSONObject getUserInfo(String encryptedData, String sessionKey, String iv){ byte[] dataByte = Base64.decode(encryptedData); byte[] keyByte = Base64.decode(sessionKey); byte[] ivByte = Base64.decode(iv);
try { int base = 16; if (keyByte.length % base != 0) { int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0); byte[] temp = new byte[groups * base]; Arrays.fill(temp, (byte) 0); System.arraycopy(keyByte, 0, temp, 0, keyByte.length); keyByte = temp; } Security.addProvider(new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC"); SecretKeySpec spec = new SecretKeySpec(keyByte, "AES"); AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES"); parameters.init(new IvParameterSpec(ivByte)); cipher.init(Cipher.DECRYPT_MODE, spec, parameters); byte[] resultByte = cipher.doFinal(dataByte); if (null != resultByte && resultByte.length > 0) { String result = new String(resultByte, "UTF-8"); return JSONObject.parseObject(result); } } catch (Exception e) { e.printStackTrace(); } return null; }
|
maven依赖引入
1 2 3 4 5
| <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.59</version> </dependency>
|
然后我们将结果打印就能得到我们解密后的信息了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| { "country":"China", "watermark":{ "appid":"你的AppId", "timestamp":1592219018 }, "gender":1, "province":"Jiangsu", "city":"Nanjing", "avatarUrl":"你的头像链接地址", "openId":"你的OpenId", "nickName":"丁生", "language":"zh_CN" }
|