笔者最近刚开始玩微信小程序,对其中的一些原理还不是很清楚,欢迎各位批评指正。
在网络中传输明文会存在一定的风险,因此想要与后台数据库结合,笔者认为不能直接调用微信认证的接口,并且将用户信息传递到后台,而应该先调用微信的方法获取加密的encrypedData、iv、code等,在后台将用户数据进行解析,并存入数据库。
总体的思路时,当启动小程序的时候,从微信服务器请求到加密数据以及code等,并携带这些数据向服务器发请求(是自己的服务器,不是微信服务器),服务器先根据code获取用户的openid(一个唯一标识,如果不清楚可以参考笔者的另外一篇文章: java实现微信登录)从缓存中查询是否有此用户对应的信息(openid),如果有则直接从缓存中将数据返回,如果没有则调用微信授权的接口,获取用户信息存入数据库和缓存,并将用户信息返回给小程序。
该项目后台为springboot+mybatisplus,下面是核心代码:
一、小程序启动的js
//app.js
App({
onLaunch: function () {
// 展示本地存储能力
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
denglu: function () {
var that = this;
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
wx.getUserInfo({
success: result => {
// 获取用户信息
wx.request({
url: '/user/start',
method: 'GET',
data: {
code: res.code,
encryptedData: result.encryptedData,
iv: result.iv
},
success: function (e) {
//console.log(e.data.data);
that.globalData.userInfo = e.data.data;
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(e.data.data);
}
}
})
}
})
}
})
},
globalData: {
userInfo: null
}
})
二、Controller层
@GetMapping("/start")
public BabyResult startApp(@RequestParam("encryptedData") String encrypted_data,
@RequestParam("iv") String iv,
@RequestParam("code") String code) {
try {
BabyResult result = userService.startApp(encrypted_data, iv, code);
return result;
} catch (Exception e) {
e.printStackTrace();
return BabyResult.build(500, "启动错误");
}
}
三、Service层
@Override
public BabyResult startApp(String encrypted_data, String iv, String code) {
try {
//获取session_key
String accessJson = this.getSessionKey(code);
if ("".equals(accessJson)) {
return BabyResult.build(401, "获取session_key失败");
}
JsonObject accessJsonObject = new JsonParser().parse(accessJson).getAsJsonObject();
String openid = accessJsonObject.get("openid").getAsString();
if(!userMap.containsKey(openid)) {
/**
* 第一次授权
*/
//解析用户微信信息
String session_key = accessJsonObject.get("session_key").getAsString();;
String userWxInfoJson = WxDataUtil.DecryptDataGetUserInfoJson(encrypted_data, session_key, iv);
if (null == userWxInfoJson || "".equals(userWxInfoJson)) {
logger.debug("获取用户微信信息失败");
return BabyResult.build(401, "获取用户微信信息失败");
}
//保存用户信息
User user = this.saveUserInfo(userWxInfoJson, openid);
//将用户信息放入缓存
userMap.put(openid, user);
//将用户信息返回给客户端
return BabyResult.ok(user);
} else {
/**
* 开启小程序,之前已经授权
*/
User user = (User) userMap.get(openid);
return BabyResult.ok(user);
}
} catch (Exception e) {
e.printStackTrace();
logger.debug("拉取登录信息失败");
return BabyResult.build(500, "拉取登录信息失败");
}
}
四、解密微信数据
public static String DecryptDataGetUserInfoJson(String encryptedData,String sessionkey,String iv){
// 被加密的数据
byte[] dataByte = Base64.decode(encryptedData);
// 加密秘钥
byte[] keyByte = Base64.decode(sessionkey);
// 偏移量
byte[] ivByte = Base64.decode(iv);
try {
// 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
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 result;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
五、总结
微信程序现在比较火,但是微信开发工具还是有很多的bug。而且大部分都是用php做后台服务,笔者也用过,确实php开发比较简单,因为小程序毕竟不会像传统应用那样具有特别复杂的逻辑;笔者主要是做java开发,因此用java写了一套服务。其实,在这个webservice的时代,大家都遵循Restful风格的API,即使不同的编程语言,也都可以相互调用。