微信小程序和以前的web项目不同,他是前后端分离的应用,之前我们的传统登录有web服务器提供Session维护, 后端在返回结果给前端web项目时,带上cookie,且以cookie值为key存储用户信息到session中,前端web项目在再以后端返回的结果中,得到相应cookie的值,再一次以同样的方式将cookie值返回给前端浏览器客户端,这样,后面每次浏览器客户端请求时都会带上cookie。微信小程序中没有cooike,那么接下来我们来看下小程序如何登录,以及如何维护微信小程序的登录态(Session)。
官方登录流程
直接引用官方的登录文档,我们的登录维护都是按照这个文档进行的开发,登录流程图如下图所示:
1、在小程序中获取微信用户的登录凭证(code)
小程序的登录直接使用官方api接口wx.login(),从微信的官方服务器换取接口凭证code。
然后在小程序的app.js和后台进行交互,将code发给自己的后台服务器进行登录换取token。
// 登录
wx.login({
success: res => {
var that = this;
// 发送 res.code 到后台换取 openId, sessionKey, unionId
console.log(res.code);
wx.setStorage({ //缓存code
key: "code",
data: res.code
})
console.log("code:" + res.code)
this.globalData.code = res.code;
var that = this;
var token = wx.getStorageSync('token') //同步取 token
console.log(token);
wx.request({
url: "http://10.5.5.118:8081/api/wechat/login",
header: {
'Authorization': token,
"Content-Type": "application/x-www-form-urlencoded" //POST的header必须是
},
method: "POST",
data: {
appType: "doctor",
code: res.code
},
success: function (res) {}
2、获取用户的openid和session_key
通过小程序请求的code,我们后台再次请求微信的服务器获取微信用户的信息,也就是openid和session_key。
try {
// url 地址微信规定只能这样拼接
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wechatConfig.getAppid() + "&secret="
+ wechatConfig.getSecret() + "&js_code=" + code + "&grant_type=authorization_code";
logger.info("发送获取openid请求url:" + url);
String result = httpApiService.doPost(url);
logger.info("请求结果:" + result);
ObjectMapper objectMapper = new ObjectMapper();
// json 转对象
return objectMapper.readValue(result, WeChatResponse.class);
} catch (Exception e) {
logger.info("获取openid失败" + e);
e.printStackTrace();
throw new ServiceException("获openid失败", CustomErrorCode.INVALID_OPENID);
}
请求地址:
https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
这里微信有一个规定,参数值只能拼接在后面,不能通过HttpClient的map参数放上去,需要注意一下。
通过这个接口的调用,可以拿到用户openid,和session_key,现在当前登录的账户就在微信的服务器中产生了登录态。
但是微信的官方文档提示开发者,为了安全起见,需要第三方服务器自己定义一个登陆态,并且把如何定义session的规则告诉了。
我们拿到了openid和session_key,将用户的openid存入数据库,通过加密算法把openid和session_key加密生成3rd_session:
// 3、获取自定义登陆态生成token,由openid和session_key生key=rd_session,value=openid
String rd_session = UUIDUtils.get16UUID();
redisTemplate.opsForValue().set(rd_session, openid);
将3rd_session作为key,openid作为value存入redis,这里的3rd_session就是token,为了用户的操作提供认证,在用户登陆成功时,在header中携带token返回给用户客户端,用户没带token请求,或者是token失效,都会请求失败。
使用拦截器处理用户的token认证,将http请求中带的认证信息和redis的对比,如果存在没失效,直接授权用户请求,失效则拦截用户的请求。
/**
* 前置处理器
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
logger.info("RequestURI:" + request.getRequestURI());
String url = request.getRequestURI();
logger.info("url: " + url);
// 注册不做拦截
for (String urlConfig : config.getLoginConfigs()) {
if (StringUtils.contains(url, urlConfig)) {
logger.info("登陆非拦截地址" + url);
// 清除上次登陆的token
if (StringUtils.isNotEmpty(token)) {
logger.info("清除上次登陆的token:" + token);
redisTemplate.delete(token);
}
return true;
}
}
// 其他地址不拦截:字典数据和文件上传的接口
for (String urlConfig : config.getOtherConfigs()) {
if (StringUtils.contains(url, urlConfig)) {
logger.info("其他非拦截地址" + url);
return true;
}
}
// 验证token是否存在
logger.info("Authorization:" + token);
if (StringUtils.isEmpty(token)) {
logger.info("token参数为空");
return false;
}
// 获取redis中的session_key和openid
String rd_session = redisTemplate.opsForValue().get(token);
if (rd_session == null) {
logger.info("token失效:" + token);
return false;
}
logger.info("token存在,验证成功!");
return true;
}
3、小程序缓存
在web开发中我们通常将session信息放入cookie中,但是小程序没有cookie,所以需要借助缓存来实现cookie功能。用户第一次登陆成功后,会返回该用户对应的token,并放入storage缓存中,每次请求都会带上去请求,向第三方服务器认证身份。
wx.setStorageSync('id', res.data.object.id)
var value = wx.getStorageSync('id')
wx.setStorageSync('token', res.header.Authorization) //同步存 token
wx.setStorageSync('openid', res.data.object.openid) //同步存 token
wx.setStorageSync('userState', res.data.object.userState)
getApp().globalData.user_wxNickname = res.data.object.wxNickname;
getApp().globalData.user_userIcon = res.data.object.userIcon
小程序的登陆就是这几个大的步骤,按照微信官方文档来开发,也需要避开一些小坑。