目录
微信授权登录流程
1. 官方图示流程详解
2. 代码登录流程拆解
2.1 前端代码示例讲解
2.2 后端代码示例讲解
2.3 代码登录流程拆解 🌟
3. 表情包存储展示(扩展)
附议
微信授权登录流程
1. 官方图示流程详解
① 微信服务器验证:当用户打开小程序时,小程序会向用户展示登录按钮,用户点击登录按钮后,小程序会向微信服务器发送登录请求(wx.login())。微信服务器接收到登录请求后,会验证小程序的身份和合法性。如果小程序通过验证,微信服务器会生成一个临时的登录凭证(code)。
code有效时间仅为5分钟,如果5分钟内小程序的后台不拿着这个临时身份证来微信后台服务器换取微信用户id的话,那么这个身份证就会被作废,需要再调用wx.login重新生成登录凭证。
② 发送code到开发者服务器:在wx.login的success回调中拿到微信登录凭证,紧接着会通过wx.request把code传到开发者服务器,为了后续可以换取微信用户身份id。
如果当前微信用户还没有绑定当前小程序业务的用户身份,那在这次请求应该顺便把用户输入的帐号密码[7]一起传到后台,然后开发者服务器就可以校验账号密码之后再和微信用户id进行绑定
③ 获取用户信息:小程序通过调用微信提供的API,使用临时登录凭证(code)向微信服务器发送请求,以获取用户的唯一标识(openid)和会话密钥(session_key)。
到微信服务器的请求要同时带上AppId和AppSecret , AppId和AppSecret是微信鉴别开发者身份的重要信息,AppId是公开信息,泄露AppId不会带来安全风险,但是AppSecret是开发者的隐私数据不应该泄露,如果发现泄露需要到小程序管理平台进行重置AppSecret,而code在成功换取一次信息之后也会立即失效,即便凭证code生成时间还没过期。
④ 绑定微信用户身份id和业务用户身份:此时开发者后台通过校验用户名密码就拿到了业务侧的用户身份id,通过code到微信服务器获取微信侧的用户身份openid。微信会建议开发者把这两个信息的对应关系存起来,我们把这个对应关系称之为“绑定”。
有了这个绑定信息,小程序在下次需要用户登录的时候就可以不需要输入账号密码,因为通过wx.login()获取到code之后,可以拿到用户的微信身份openid,通过绑定信息就可以查出业务侧的用户身份id,这样静默授权的登录方式显得非常便捷。
⑤⑥⑦ 业务登录凭证SessionId:用户登录成功之后,开发者服务器需要生成会话密钥SessionId,在服务端保持SessionId对应的用户身份信息,同时把SessionId返回给小程序。小程序后续发起的请求中携带上SessionId,开发者服务器就可以通过服务器端的Session信息查询到当前登录用户的身份,这样我们就不需要每次都重新获取code,省去了很多通信消耗。
2. 代码登录流程拆解
2.1 前端代码示例讲解
login.js文件:
// pages/auth/login/login.jsvar util = require('../../../utils/util.js');var user = require('../../../utils/user.js');const app = getApp();Page({ /** * 页面的初始数据 */ data: { canIUseGetUserProfile: false, // 用于向前兼容 lock:false }, onLoad: function(options) { // 页面初始化 options为页面跳转所带来的参数 // 页面渲染完成 if (wx.getUserProfile) { this.setData({ canIUseGetUserProfile: true }) } //console.log('login.onLoad.canIUseGetUserProfile='+this.data.canIUseGetUserProfile) }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { }, /** * 生命周期函数--监听页面显示 */ onShow() { }, getUserProfile(e) { // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 wx.getUserProfile({ desc: '用于完善会员资料', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 success: (res) => { //console.log(res); debugger user.checkLogin().catch(() => { user.loginByWeixin(res.userInfo).then(res => { app.globalData.hasLogin = true; debugger wx.navigateBack({ delta: 1 }) }).catch((err) => { app.globalData.hasLogin = false; if(err.errMsg=="request:fail timeout"){ util.showErrorToast('微信登录超时'); }else{ util.showErrorToast('微信登录失败'); } this.setData({ lock:false }) }); }); }, fail: (res) => { app.globalData.hasLogin = false; console.log(res); util.showErrorToast('微信登录失败'); } }); }, wxLogin: function(e) { if (e.detail.userInfo == undefined) { app.globalData.hasLogin = false; util.showErrorToast('微信登录失败'); return; } user.checkLogin().catch(() => { user.loginByWeixin(e.detail.userInfo).then(res => { app.globalData.hasLogin = true; wx.navigateBack({ delta: 1 }) }).catch((err) => { app.globalData.hasLogin = false; if(err.errMsg=="request:fail timeout"){ util.showErrorToast('微信登录超时'); }else{ util.showErrorToast('微信登录失败'); } }); }); }, accountLogin() { console.log('开发中....') }})
这段代码的作用是实现微信小程序的登录功能。当用户点击登录按钮时,会触发getUserProfile或wxLogin方法,通过获取用户信息并调用登录接口,实现用户的登录操作。登录成功后,会将登录状态保存在全局变量中,并返回上一页。
utils文件夹(封装了登录等方法的工具类) --> user.js文件:
/** * 用户相关服务 */const util = require('../utils/util.js');const api = require('../config/api.js');/** * Promise封装wx.checkSession */function checkSession() { return new Promise(function(resolve, reject) { wx.checkSession({ success: function() { resolve(true); }, fail: function() { reject(false); } }) });}/** * Promise封装wx.login */function login() { return new Promise(function(resolve, reject) { wx.login({ success: function(res) { if (res.code) { resolve(res); } else { reject(res); } }, fail: function(err) { reject(err); } }); });}/** * 调用微信登录 */function loginByWeixin(userInfo) { return new Promise(function(resolve, reject) { return login().then((res) => { //登录远程服务器 util.request(api.AuthLoginByWeixin, { code: res.code, userInfo: userInfo }, 'POST').then(res => { if (res.errno === 0) { //存储用户信息 wx.setStorageSync('userInfo', res.data.userInfo); wx.setStorageSync('token', res.data.token); resolve(res); } else { reject(res); } }).catch((err) => { reject(err); }); }).catch((err) => { reject(err); }) });}/** * 判断用户是否登录 */function checkLogin() { return new Promise(function(resolve, reject) { if (wx.getStorageSync('userInfo') && wx.getStorageSync('token')) { checkSession().then(() => { resolve(true); }).catch(() => { reject(false); }); } else { reject(false); } });}module.exports = { loginByWeixin, checkLogin,};
loginByWeixin(userInfo):这个函数用于进行微信登录操作。它首先调用login()函数获取微信登录凭证(code),然后将凭证和用户信息一起发送给服务器进行登录验证。如果登录成功,将用户信息和令牌(token)存储在本地缓存中,并通过resolve返回成功的结果。如果登录失败,将通过reject返回失败的结果。
checkLogin():这个函数用于判断用户是否已经登录。它首先检查本地缓存中是否存在用户信息和令牌,如果存在,则调用checkSession()函数检查用户的登录状态。如果登录状态有效,通过resolve返回登录状态为true,表示用户已登录。如果登录状态无效或本地缓存中不存在用户信息和令牌,通过reject返回登录状态为false,表示用户未登录。
通过这两个函数,user模块提供了登录和登录状态检查的功能,可以在其他地方调用这些函数来实现用户登录和登录状态的管理。
2.2 后端代码示例讲解
package com.ycxw.ssm.wxcontroller;/** * @Autho 云村小威 * @Since 2023/10/22 */import cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;import com.alibaba.fastjson.JSONObject;import com.ycxw.ssm.annotation.LoginUser;import com.ycxw.ssm.model.UserInfo;import com.ycxw.ssm.model.WxLoginInfo;import com.ycxw.ssm.model.WxUser;import com.ycxw.ssm.service.UserToken;import com.ycxw.ssm.service.UserTokenManager;import com.ycxw.ssm.service.WxUserService;import com.ycxw.ssm.util.JacksonUtil;import com.ycxw.ssm.util.ResponseUtil;import com.ycxw.ssm.util.UserTypeEnum;import lombok.extern.slf4j.Slf4j;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.util.StringUtils;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import cn.binarywang.wx.miniapp.api.WxMaService;import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;import javax.servlet.http.HttpServletRequest;import java.text.DateFormat;import java.text.SimpleDateFormat;import java.util.Date;import java.util.HashMap;import java.util.Map;/** * 鉴权服务 */@Slf4j@RestController@RequestMapping("/wx/auth")public class WxAuthController { @Autowired private WxMaService wxService; @Autowired private WxUserService userService; /** * 微信登录 * * @param wxLoginInfo * 请求内容,{ code: xxx, userInfo: xxx } * @param request * 请求对象 * @return 登录结果 */ @PostMapping("login_by_weixin") public Object loginByWeixin(@RequestBody WxLoginInfo wxLoginInfo, HttpServletRequest request) { //客户端需携带code与userInfo信息 String code = wxLoginInfo.getCode(); UserInfo userInfo = wxLoginInfo.getUserInfo(); if (code == null || userInfo == null) { return ResponseUtil.badArgument(); } //调用微信sdk获取openId及sessionKey String sessionKey = null; String openId = null; try { long beginTime = System.currentTimeMillis(); // WxMaJscode2SessionResult result = this.wxService.getUserService().getSessionInfo(code);// Thread.sleep(6000); long endTime = System.currentTimeMillis(); log.info("响应时间:{}",(endTime-beginTime)); sessionKey = result.getSessionKey();//session id openId = result.getOpenid();//用户唯一标识 OpenID } catch (Exception e) { e.printStackTrace(); } if (sessionKey == null || openId == null) { log.error("微信登录,调用官方接口失败:{}", code); return ResponseUtil.fail(); }else{ log.info("openId={},sessionKey={}",openId,sessionKey); } //根据openId查询wx_user表 //如果不存在,初始化wx_user,并保存到数据库中 //如果存在,更新最后登录时间 WxUser user = userService.queryByOid(openId); if (user == null) { user = new WxUser(); user.setUsername(openId); user.setPassword(openId); user.setWeixinOpenid(openId); user.setAvatar(userInfo.getAvatarUrl()); user.setNickname(userInfo.getNickName()); user.setGender(userInfo.getGender()); user.setUserLevel((byte) 0); user.setStatus((byte) 0); user.setLastLoginTime(new Date()); user.setLastLoginIp(IpUtil.client(request)); user.setShareUserId(1); userService.add(user); } else { user.setLastLoginTime(new Date()); user.setLastLoginIp(IpUtil.client(request)); if (userService.updateById(user) == 0) { log.error("修改失败:{}", user); return ResponseUtil.updatedDataFailed(); } } // token UserToken userToken = null; try { userToken = UserTokenManager.generateToken(user.getId()); } catch (Exception e) { log.error("微信登录失败,生成token失败:{}", user.getId()); e.printStackTrace(); return ResponseUtil.fail(); } userToken.setSessionKey(sessionKey); log.info("SessionKey={}",UserTokenManager.getSessionKey(user.getId())); Map<Object, Object> result = new HashMap<Object, Object>(); result.put("token", userToken.getToken()); result.put("tokenExpire", userToken.getExpireTime().toString()); userInfo.setUserId(user.getId()); if (!StringUtils.isEmpty(user.getMobile())) {// 手机号存在则设置 userInfo.setPhone(user.getMobile()); } try { DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); String registerDate = df.format(user.getAddTime() != null ? user.getAddTime() : new Date()); userInfo.setRegisterDate(registerDate); userInfo.setStatus(user.getStatus()); userInfo.setUserLevel(user.getUserLevel());// 用户层级 userInfo.setUserLevelDesc(UserTypeEnum.getInstance(user.getUserLevel()).getDesc());// 用户层级描述 } catch (Exception e) { log.error("微信登录:设置用户指定信息出错:"+e.getMessage()); e.printStackTrace(); } result.put("userInfo", userInfo); log.info("【请求结束】微信登录,响应结果:{}", JSONObject.toJSONString(result)); return ResponseUtil.ok(result); } }
这段代码是一个微信小程序的登录接口,使用了Spring Boot框架进行开发。
loginByWeixin方法:这是一个处理微信登录请求的方法。它接收一个WxLoginInfo对象作为参数,该对象包含了前端传递的code和userInfo信息。方法首先判断code和userInfo是否为空,如果为空则返回错误响应。然后调用微信SDK的getUserService().getSessionInfo(code)方法,通过code获取sessionKey和openId。接下来根据openId查询数据库中的wx_user表,如果用户不存在,则创建一个新的用户并保存到数据库中;如果用户存在,则更新最后登录时间。然后生成用户的token,并将token和用户信息返回给前端。
返回结果:将登录结果封装在一个Map对象中,包括token、tokenExpire和userInfo。最后将结果返回给前端。
2.3 代码登录流程拆解 🌟
① 首先用户点击登录并授权开始调用wx.getUserProfile获取用户信息方法 --> checkLogin()判断用户是否登录过,没有将调用wx.login方法 获取 临时登录凭证code
② 紧接着会通过wx.request把code传到开发者服务器,为了后续可以换取微信用户身份id。
③ 使用临时登录凭证(code)向微信服务器发送请求,以获取用户的唯一标识(openid)和会话密钥(session_key)。
wxService:调用 auth.code2Session 接口,换取 用户唯一标识 OpenID
④ 接下来根据openId查询数据库中的wx_user表,如果用户不存在,则创建一个新的用户并保存到数据库中;如果用户存在,则更新最后登录时间。然后生成用户的token,并将token和用户信息返回给前端。
表设计:
token和用户信息返回给前端:
⑤ 登录成功通过后保存SessionId以及对应的用户身份信息
数据库储存数据:
⑥ 下次再次发送登录请求时通过wx.getStorageSync()从本地缓存中同步获取指定用户信息和token的内容
再次登录时将会修改登录时间
案例演示:
再次登录时间更新:
3. 表情包存储展示(扩展)
在存储用户信息时可能有些用户名存在一些emoji小表情,在mysql中utf8编码的一个字符最多3个字节,但是一个emoji表情为4个字节,所以utf8不支持存储emoji表情。但是utf8的超集utf8mb4一个字符最多能有4字节,所以能支持emoji表情的存储。
修改编码集:
Linux系统中MySQL的配置文件为my.cnf。
Winows中的配置文件为my.ini。
注意:在创建数据库时也要选择utf8mb4
附议
源码: