【微信小程序】授权登录流程解析

news2024/11/18 19:46:47

 

目录

微信授权登录流程

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)。

到微信服务器的请求要同时带上AppIdAppSecret , 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.js
var 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,
};
  1. loginByWeixin(userInfo):这个函数用于进行微信登录操作。它首先调用login()函数获取微信登录凭证(code),然后将凭证和用户信息一起发送给服务器进行登录验证。如果登录成功,将用户信息和令牌(token)存储在本地缓存中,并通过resolve返回成功的结果。如果登录失败,将通过reject返回失败的结果。

  2. 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框架进行开发。

  1. loginByWeixin方法:这是一个处理微信登录请求的方法。它接收一个WxLoginInfo对象作为参数,该对象包含了前端传递的code和userInfo信息。方法首先判断code和userInfo是否为空,如果为空则返回错误响应。然后调用微信SDK的getUserService().getSessionInfo(code)方法,通过code获取sessionKey和openId。接下来根据openId查询数据库中的wx_user表,如果用户不存在,则创建一个新的用户并保存到数据库中;如果用户存在,则更新最后登录时间。然后生成用户的token,并将token和用户信息返回给前端。

  2. 返回结果:将登录结果封装在一个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

附议 

源码:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1120846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

excel单元格各种组合求和

单元格如果连续选择的话使用冒号&#xff0c;不是连续选择使用逗号&#xff1b;sum(A1:A4)表示对A1到A4求和&#xff1b;sum(A1,A4)表示求A1A4的和&#xff1b; 如下图&#xff0c;求斜线上四个单元格的和&#xff0c;结果见下图&#xff1b; 求A列和C列全部单元格的和&#x…

Python 函数:定义、调用、参数、递归和 Lambda 函数详解

函数是一段代码块&#xff0c;只有在调用时才会运行。您可以将数据&#xff08;称为参数&#xff09;传递给函数。 函数可以返回数据作为结果。 创建函数 在Python中&#xff0c;使用def关键字定义函数&#xff1a; 示例 def my_function():print("Hello from a func…

CCC数字钥匙设计【NFC】--NFC卡相关基础知识

CCC3.0的NFC技术中&#xff0c;除车端&#xff0c;手机端需包含NFC功能外&#xff0c;另外一般还会配置一个NFC卡&#xff0c;用于备份使用。本文主要介绍NFC卡相关的基础知识。 1、NFC卡 & 智能卡 NFC卡是一种智能卡&#xff0c;其与信用卡大小相同&#xff0c;可通过嵌入…

删除所有出现次数最少的字符

题意: 假设字符串中出现次数最少的字母是x, 出现次数为y, 删除所有出现次数为y的字符 思路&#xff1a;用unordered_map统计出出现次数最少的x出现的次数y 再遍历字符串&#xff0c;删除所有出现次数为y的字符 代码&#xff1a; #include <iostream> #include <uno…

stm32外部时钟为12MHZ,修改代码适配

代码默认是8MHZ的&#xff0c;修改2个地方&#xff1a; 第一个地方是这个文件的这里&#xff1a; 第二个地方是找到这个函数&#xff1a; 修改第二个地方的这里&#xff1a;

图论06-【无权无向】-图的遍历并查集Union Find-力扣695为例

文章目录 1. 代码仓库2. 思路2.1 UF变量设计2.2 UF合并两个集合2.3 查找当前顶点的父节点 find(element) 3. 完整代码 1. 代码仓库 https://github.com/Chufeng-Jiang/Graph-Theory 2. 思路 2.1 UF变量设计 parent数组保存着每个节点所指向的父节点的索引&#xff0c;初始值为…

Xubuntu16.04系统中安装create_ap创建无线AP

1.背景说明 在Xubuntu16.04系统的设备上安装无线WIFI模块后&#xff0c;想通过设备自身的无线AP&#xff0c;进行和外部设备的连接&#xff0c;需要安装create_ap软件&#xff0c;并设置无线AP的名称和密码&#xff0c;并设置为开机自启动。 create_ap是一个用于在Linux系统上创…

LeetCode58——最后一个单词的长度

自己的解&#xff1a; public static int lastLength(String s){//返回通过围绕给定正则表达式的匹配拆分此字符串计算的字符串数组String[] str s.split(" ");//将数组最后一个元素 即最后一个单词通过toCharArray方法拆分到char数组中char[] last str[str.lengt…

【JavaEE重点知识归纳】第10节:Object类和String类

目录 一&#xff1a;Object类 1.概念 2.获取对象信息 3.对象比较equals方法 4.hashCode方法 二&#xff1a;String类 1.String类的重要性 2.常用方法 3.StringBuilder和StringBuffer 一&#xff1a;Object类 1.概念 &#xff08;1&#xff09;Object类是Java默认提供…

剑指Offer || 052.递增顺序搜索树

题目 给你一棵二叉搜索树&#xff0c;请 按中序遍历 将其重新排列为一棵递增顺序搜索树&#xff0c;使树中最左边的节点成为树的根节点&#xff0c;并且每个节点没有左子节点&#xff0c;只有一个右子节点。 示例 1&#xff1a; 输入&#xff1a;root [5,3,6,2,4,null,8,1,n…

hackbar基于插件的网络渗透测试工具

一、安装 安装&#xff1a;通过Firefox在插件扩展里面找到hackbar v2进行安装(注意hackerbar是收费的&#xff1b;v2不是) 安装成功示例&#xff1a; 二、基本使用 1.Load URL 解释&#xff1a;将当前网页的url自动填充到多行文本框里面 2.Split URL 解释&#xff1a;自动…

22下半年下午题

声明&#xff1a;哔哩哔哩视频笔记 源地址 第一大题题目 第一大题解答 第一小问 根据0层数据流图来找&#xff0c;看数据流向和相应的处理模块匹配。并且这个第一问&#xff0c;肯定是能在说明中找到对应短语作为答案的。 第二小问 搞清楚具体存储数据的信息名字&#xff…

数据结构:二叉树(3):相关oj题目

二叉树oj题的续 目录 102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; 236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; 方法1 方法2 105. 从前序与中序遍历序列构造二叉树 - 力扣&#xff08;LeetCode&#xff09; 106. 从中序与后序…

图论03-【无权无向】-图的深度优先DFS遍历-路径问题/检测环/二分图

文章目录 1. 代码仓库2. 单源路径2.1 思路2.2 主要代码 3. 所有点对路径3.1 思路3.2 主要代码 4. 路径问题的优化-提前结束递归4.1 思路4.2 主要代码 5. 检测环5.1 思路5.2 主要代码 6. 二分图6.1 思路6.2 主要代码6.2.1 遍历每个联通分量6.2.2 递归判断相邻两点的颜色是否一致…

java--Consumer的使用

一、目的 因为代码中用到了Consumer &#xff0c;所以特地来梳理其对应的用法以及总结。 二、介绍 Consumer 是一个函数式接口&#xff0c;它位于java.util.function 包中&#xff0c;它定义了一个名为accept 的抽象方法&#xff0c;该方法接受一个参数并且不返回任何结果。 …

【C++和数据结构】模拟实现哈希表和unordered_set与unordered_map

目录 一、哈希的概念与方法 1、哈希概念 2、常用的两个哈希函数 二、闭散列的实现 1、基本结构&#xff1a; 2、两种增容思路 和 插入 闭散列的增容&#xff1a; 哈希表的插入&#xff1a; 3、查找 4、删除 三、开散列的实现 1、基本结构 2、仿函数Hash 3、迭代器…

sealos 部署halo

首先创建数据库 postgresql 找到连接数据库&#xff0c;点击详情查看数据库的参数 部署halo应用 halohub/halo:2.5 高级配置里设置环境变量 本地存储 配置 Halo 环境变量环境变量 spring.sql.init.platformpostgresql spring.r2dbc.urlr2dbc:pool:postgresql://postgres:4w…

在 Python 中使用 Pillow 进行图像处理【3/4】

第三部分 一、腐蚀和膨胀 您可以查看名为 的图像文件dot_and_hole.jpg&#xff0c;您可以从本教程链接的存储库中下载该文件&#xff1a; 该二值图像的左侧显示黑色背景上的白点&#xff0c;而右侧显示纯白色部分中的黑洞。 侵蚀是从图像边界去除白色像素的过程。您可以通过使用…

运算符重载的三种实现方法

一、重载为一般函数 格式&#xff1a;返回类型 operator 运算符(参数列表) struct Complex{//定义一个复数结构&#xff1a;包括实部与虚部两部分 double real;//实部 double imag;//虚部 }; Complex operator(Complex c1,Complex c2){//对加法运算的重载&#xff1a;将运算符…

扩展字符串(acwing周赛第三题)

代码&#xff1a; #include <bits/stdc.h> using namespace std; typedef long long LL; const int N 1e510; const LL INF 2e18; LL len[N];string s"DKER EPH VOS GOLNJ ER RKH HNG OI RKH UOPMGB CPH VOS FSQVB DLMM VOS QETH SQB"; string a"DKER …