微信公众号自动登录方案

news2024/9/30 1:31:07

基于微信公众号登录

借助微信公众号来试实现社区登录。登录的时候展示的是一个二维码,但实际上的操作是借助这个展示的过程,和前端构建一个半长连接,当用户向公众号发送验证码之后,微信公众平台会将用户发送的消息转发给服务器,通过验证码来识别请求登录的用户身份,找到对应的半长连接,实现用户的自动登录跳转。

方案设计

在这里插入图片描述
基于上面的方案,用户表中需要存储一个核心的用户标识。

  • uuid:微信公众平台返回的用于唯一标识

优点

  • 对于用户而言登录方式简单,无需记忆密码,用户名,有微信号即可

缺点

  • 实现方式相对复杂 ;
  • 个人公众号不支持自定义二维码参数,因此需要输入验证码。(企业公众号可以实现扫码之后直接自动登录,无需输入验证码);

方案准备

一个微信公众号;
一台微信公众平台可以回调的服务器。

方案实现

1. 微信公众平台配置
直接登录后台,开启服务器相关配置:. 在这里插入图片描述
微信公众平台接入时,需要进行一个token验证,即返回他传参的echostr

    /**
     * 微信的公众号接入 token 验证,即返回echostr的参数值
     *
     * @param request
     * @return
     */
    @GetMapping(path = "callback")
    public String check(HttpServletRequest request) {
        String echoStr = request.getParameter("echostr");
        if (StringUtils.isNoneEmpty(echoStr)) {
            return echoStr;
        }
        return "";
    }

除此之外,还需要实现的是接收微信公众平台的回调,注意微信公众号采用的是xml进行通讯。
实现的接口如下:

   /**
     * fixme: 需要做防刷校验
     * 微信的响应返回
     * 本地测试访问: curl -X POST 'http://localhost:8080/wx/callback' -H 'content-type:application/xml' -d '<xml><URL><![CDATA[https://hhui.top]]></URL><ToUserName><![CDATA[一灰灰blog]]></ToUserName><FromUserName><![CDATA[demoUser1234]]></FromUserName><CreateTime>1655700579</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[login]]></Content><MsgId>11111111</MsgId></xml>' -i
     *
     * @param msg
     * @return
     */
    @PostMapping(path = "callback",
            consumes = {"application/xml", "text/xml"},
            produces = "application/xml;charset=utf-8")
    public BaseWxMsgResVo callBack(@RequestBody WxTxtMsgReqVo msg) {
    }

2. 用户扫码登录
需要特别注意的是,当用户点击登录后,弹出一个微信公众号的二维码的同时,我们需要建立一个与前端的半长连接,目的是用于后续的自动登录跳转。
这里设计了两个接口:一个是获取登录的验证码,一个是建立半长连接。
验证码获取:
在这里插入图片描述

 /**
     * 获取登录的验证码
     *
     * @return
     */
    @GetMapping(path = "/login/code")
    public ResVo<QrLoginVo> qrLogin(HttpServletRequest request, HttpServletResponse response) {
        QrLoginVo vo = new QrLoginVo();
        vo.setCode(qrLoginHelper.genVerifyCode(request, response));
        return ResVo.ok(vo);
    }

    /**
     * 加一层设备id,主要目的就是为了避免不断刷新页面时,不断的往 verifyCodeCache 中塞入新的kv对
     * 其次就是确保五分钟内,不管刷新多少次,验证码都一样
     *
     * @param request
     * @param response
     * @return
     */
    public String genVerifyCode(HttpServletRequest request, HttpServletResponse response) {
        String deviceId = initDeviceId(request, response);
        String code = deviceCodeCache.getUnchecked(deviceId);
        SseEmitter lastSse = verifyCodeCache.getIfPresent(code);
        if (lastSse != null) {
            // 这个设备之前已经建立了连接,则移除旧的,重新再建立一个; 通常是不断刷新登录页面,会出现这个场景
            lastSse.complete();
            verifyCodeCache.invalidate(code);
        }
        return code;
    }

对于验证码的获取,做了一个兼容策略,同一个设备,不论访问多少次验证码都是同一个(刷新除外),所以做了两个缓存:

  • deviceCodeCache: 缓存deviceId设备验证码之间的映射关系
  • verifyCodeCache: 缓存code验证码半长连接之间的映射关系

半长连接建立:
在这里插入图片描述

    /**
     * 客户端与后端建立扫描二维码的长连接
     *
     * @param code
     * @return
     */
    @GetMapping(path = "subscribe", produces = {org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE})
    public SseEmitter subscribe(@RequestParam(name = "id") String code) throws IOException {
        return qrLoginHelper.subscribe(code);
    }
    /**
     * 保持与前端的长连接
     * <p>
     * 直接根据设备拿之前初始化的验证码,不直接使用传过来的code
     *
     * @param code
     * @return
     */
    public SseEmitter subscribe(String code) throws IOException {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse res = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        String device = initDeviceId(req, res);
        String realCode = deviceCodeCache.getUnchecked(device);

        // fixme 设置15min的超时时间, 超时时间一旦设置不能修改;因此导致刷新验证码并不会增加连接的有效期
        SseEmitter sseEmitter = new SseEmitter(15 * 60 * 1000L);
        verifyCodeCache.put(code, sseEmitter);
        sseEmitter.onTimeout(() -> verifyCodeCache.invalidate(realCode));
        sseEmitter.onError((e) -> verifyCodeCache.invalidate(realCode));
        if (!Objects.equals(realCode, code)) {
            // 若实际的验证码与前端显示的不同,则通知前端更新
            sseEmitter.send("initCode!");
            sseEmitter.send("init#" + realCode);
        }
        return sseEmitter;
    }

上面就是一个简单的半长连接的建立过程;并会保存一个验证码与半长连接sseEmitter之间的映射关系;后续在登录时,就可以通过验证码找到对应的SseEmitter,从而实现登录。
说明
验证码获取和半长连接建立这两个接口是搭配使用:

  • 前端首先调用获取验证码接口->这里将设备与验证码建立映射,并会释放之前建立的半长连接,返回验证码
  • 基于验证码来建立半长连接,此时构建了验证码与半长连接的映射,因此后续登录时,直接可以通过验证码查到对应的连接客户端,从而实现登录。

3. 前端调用
核心实现如下

    function loginCode() {
      $.ajax({
        url: "/login/code", dataType: "json", type: "get", success: function (data) {
          const code = data['result']['code'];
          buildConnect(code);
          if ([[${!#strings.equals(global.env, 'prod')}]]) {
            $("#mockLogin").attr('data-verify-code', code);
            $("#mockLogin2").attr('data-verify-code', code);
          }
        }
      })
    }
        /**
     * 建立半长连接,用于实现自动登录
     * @param code
     */
    function buildConnect(code) {
      const subscribeUrl = "/subscribe?id=" + code;
      const source = new EventSource(subscribeUrl);

      source.onmessage = function (event) {
        let text = event.data;
        console.log("receive: " + text);

        if (text.startsWith('refresh#')) {
          // 刷新验证码
          const newCode = text.substring(8).trim();
          codeTag.text(newCode);
          stateTag.text("已刷新");
          if ([[${!#strings.equals(global.env, 'prod')}]]) {
            $("#mockLogin").attr('data-verify-code', newCode);
            $("#mockLogin2").attr('data-verify-code', newCode);
          }
        } else if (text === 'scan') {
          // 二维码扫描
          stateTag.text('已扫描');
        } else if (text.startsWith('login#')) {
          // 登录格式为 login#cookie
          if(autoRefresh) {
            window.clearInterval(autoRefresh);
          }
          console.log("登录成功,保存cookie", text)

          document.cookie = text.substring(6);
          source.close();
          refreshPage();

        } else if (text.startsWith("init#")) {
          const newCode = text.substring(5).trim();
          codeTag.text(newCode);
          console.log("初始化验证码: ", newCode);
        }
      };

      source.onopen = function (evt) {
        console.log("开始订阅");
      }
      source.onerror = function (evt) {
        console.log("连接错误,重新开始", evt)
        buildConnect(code);
      }
      codeTag.text(code);

      if(autoRefresh) {
        window.clearInterval(autoRefresh);
      }
      // 先关闭自动刷新验证码
      // autoRefresh = setInterval(function () {
      //     refreshCode();
      // }, 5 * 60 * 1000)
    }

4. 微信公众号回调实现自动登录
用户登录之后 ——》与后端建立半长连接
接下来就是用户将验证码发送给公众号,然后公众号将用户输入转发给技术派后端注册的回调接口

    /**
     * fixme: 需要做防刷校验
     * 微信的响应返回
     * 本地测试访问: curl -X POST 'http://localhost:8080/wx/callback' -H 'content-type:application/xml' -d '<xml><URL><![CDATA[https://hhui.top]]></URL><ToUserName><![CDATA[一灰灰blog]]></ToUserName><FromUserName><![CDATA[demoUser1234]]></FromUserName><CreateTime>1655700579</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[login]]></Content><MsgId>11111111</MsgId></xml>' -i
     *
     * @param msg
     * @return
     */
    @PostMapping(path = "callback",
            consumes = {"application/xml", "text/xml"},
            produces = "application/xml;charset=utf-8")
    public BaseWxMsgResVo callBack(@RequestBody WxTxtMsgReqVo msg) {
        String content = msg.getContent();
        if ("subscribe".equals(msg.getEvent()) || "scan".equalsIgnoreCase(msg.getEvent())) {
            String key = msg.getEventKey();
            if (StringUtils.isNotBlank(key) || key.startsWith("qrscene_")) {
                // 带参数的二维码,扫描、关注事件拿到之后,直接登录,省却输入验证码这一步
                // fixme 带参数二维码需要 微信认证,个人公众号无权限
                String code = key.substring("qrscene_".length());
                String verifyCode = sessionService.autoRegisterAndGetVerifyCode(msg.getFromUserName());
                qrLoginHelper.login(code, verifyCode);
                WxTxtMsgResVo res = new WxTxtMsgResVo();
                res.setContent("登录成功");
                fillResVo(res, msg);
                return res;
            }
        }

        BaseWxMsgResVo res = wxHelper.buildResponseBody(msg.getEvent(), content, msg.getFromUserName());
        fillResVo(res, msg);
        return res;
    }

登录逻辑如下

 // 微信公众号登录
        if (CodeGenerateUtil.isVerifyCode(content)) {
            String verifyCode = sessionService.autoRegisterAndGetVerifyCode(fromUser);
            if (qrLoginHelper.login(content, verifyCode)) {
                textRes = "登录成功";
            } else {
                textRes = "验证码过期了,刷新登录页面重试一下吧";
            }
        }

小结

还有一些细节,比如缓存结构的设计,用户名、头像自动生成选择策略,微信公众平台接入,半长连接等。

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

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

相关文章

Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)

文章目录 Unity进阶--通过PhotonServer实现联网登录注册功能(服务器端)--PhotonServer(二)服务器端大体结构图BLL层&#xff08;控制层&#xff09;DAL层&#xff08;数据控制层&#xff09;模型层DLC 服务器配置类 发送消息类 以及消息类 Unity进阶–通过PhotonServer实现联网…

Gartner发布《2023年全球RPA魔力象限》:90%RPA厂商,将提供生成式AI自动化

8月3日&#xff0c;全球著名咨询调查机构Gartner发布了《2023年全球RPA魔力象限》&#xff0c;通过产品能力、技术创新、市场影响力等维度&#xff0c;对全球16家卓越RPA厂商进行了深度评估。 弘玑Cyclone&#xff08;Cyclone Robotics&#xff09;、来也&#xff08;Laiye&am…

【蓝图】p47下车减速功能

p47下车减速功能 p47下车减速功能加速功能下车减速功能 p47下车减速功能 加速功能 上图是ue自带的加速功能&#xff0c;检测到按w时输入轴会传1给设置油门输入&#xff0c;就会加速 所以&#xff0c;减速也可以通过蓝图反方向制作 下车减速功能 打开Sedan蓝图类的上下车图表…

day51-Mybatis-Plus/代码生成器

1.Mybatis-Plus 定义&#xff1a;是一个Mybatis的增强工具&#xff0c;只在Mybatis基础上增强不做改变&#xff0c;简化开发&#xff0c;提升效率 2.MP实战 2.1 创建springboot工程&#xff0c;勾选web&#xff0c;引入依赖 <dependency> <groupId>mysql<…

人工智能可解释性分析导论(初稿)

目录 思维导图 1.黑箱所带来的问题 2.从应用面论述为什么要进行可解释性分析 2.1可解释性分析指什么 2.2可解释性分析结合人工智能应用实例 2.3 可解释性分析的脑回路&#xff08;以可视化为例如何&#xff09; 3.如何研究可解释性分析 3.1使用好解释的模型 3.2传统机器学…

antDv table组件滚动截图方法的实现

在开发中经常遇到table内容过多产生滚动的场景&#xff0c;正常情况下不产生滚动进行截图就很好实现&#xff0c;一旦产生滚动就会变得有点棘手。 下面分两种场景阐述解决的方法过程 场景一&#xff1a;右侧不固定列的情况 场景二&#xff1a;右侧固定列的情况 场景一 打开…

理解树的结构

树的重要性 二分查找算法、几种核心的排序算法以及图算法都与树有非常密切的关系。有句话锁&#xff0c;“没学会树&#xff0c;算法相当于白学”&#xff0c;可见&#xff0c;树在算法中的地位。 树的考察方面 层次遍历以及拓展问题 前后序遍历与拓展问题 中序遍历与搜索树问…

数据结构入门指南:带头双向循环链表

目录 文章目录 前言 1.结构与优势 2.链表实现 2.1 定义链表 2.2 创建头节点 2.3 尾插 2.4 输出链表 2.5 尾删 2.6 头插 2.7头删 2.8 节点个数 2.9 查找 2.10 位置插入 2.11 位置删除 2.12 销毁链表 3. 源码 总结 前言 链表一共有8种结构&#xff0c;但最常用的就是无头单…

Docker网络模型使用详解(2)Docker网络模式

安装Docker时会自动创建3个网络&#xff0c;可以使用docker network ls命令列出这些网络。 [rootlocalhost ~]# docker network ls NETWORK ID NAME DRIVER SCOPE ebcfad6f4255 bridge bridge local b881c67f8813 compose_lnmp_lnmp…

Vue2升级Vue3报错:Right-hand side of ‘instanceof‘ is not an object

属性prop设置多类型报错&#xff1a; Vue2 写法&#xff1a;支持用竖线隔开。Vue2 Prop expandLevel: {type: Number | String,default: 1, }, Vue3 写法&#xff1a;改为数组&#xff0c;不支持竖线隔开。Vue3 Prop expandLevel: {type: [Number, String],default: 1, }

二次元美少女【InsCode Stable Diffusion 美图活动一期】

目录 Stable Diffusion 模型在线使用地址 一、背景介绍 二、模板介绍&#xff1a; 三、操作步骤 1.在线运行地址 2.进入在线运行网址&#xff0c;并点击运行及使用 3.购买GPU并创建项目 4.打开工作台并选择算力资源 5.点击下图中所示框框 6.进入Stable Diffusion WebU…

VR内容研发公司 | VR流感病毒实验虚拟现实课件

由广州华锐互动开发的《VR流感病毒实验虚拟现实课件》是一种新型的教学模式&#xff0c;可以为学生提供更加真实和直观的流感病毒分离鉴定实验操作体验&#xff0c;从而提高学生的实验技能和工作效率。 《VR流感病毒实验虚拟现实课件》涉及了生物安全二级实验室(BSL-2)和流感病…

.jpeg转.jpg,cv2.resize()

from PIL import Image import os # 定义原文件夹路径和目标文件夹路径 source_folder "path/to/source/folder" target_folder "path/to/target/folder" # 遍历原文件夹中的所有图片文件 for filename in os.listdir(source_folder): if fil…

【iOS安全】安装Filza || 安装Flexdecrypt

&#xff08;成功&#xff09;使用Cydia安装Filza 直接在Cydia里搜索filza&#xff0c;安装“Filza File Manager” 使用Filza安装flexdecrypt 参考&#xff1a; https://github.com/JohnCoates/flexdecrypt 下载flexdecrypt.deb到手机&#xff1a; https://github.com/JohnC…

猎聘:2023届高校毕业生就业数据报告(附下载

关于报告的所有内容&#xff0c;公众【营销人星球】获取下载查看 核心观点 较 2022 届应届生职位同比增长较明显的TOP5 一级行业为能源/化工/环保、医疗健康、汽车、机械/制造、电子/通信/半导体&#xff0c;其中能源/化工/环保同比增长为 42.30%&#xff0c;增速最高.在全世…

mybatisplus实现自动填充 时间

mybatisplus实现自动填充功能——自动填充时间 数据库表中的字段 创建时间 (createTime)更新时间 (updateTime) 每次 增删改查的时候&#xff0c;需要通过对Entity的字段&#xff08;createTime&#xff0c;updateTime&#xff09;进行set设置&#xff0c;但是&#xff0c;每…

Systemui的介绍以及与普通应用的差异

一.SystemUI的介绍 简介 SystemUI是Android操作系统的一个关键组件&#xff0c;主要负责管理和提供用户界面的核心元素&#xff0c;如状态栏、导航栏和锁屏界面等。从下面两点出发了解SystemUI的特性&#xff1a; 一下就是systemui的部分界面&#xff0c;还包括锁屏界面&…

Android Tencent Shadow 插件接入指南

Android Tencent Shadow 插件接入指南 插件化简述一、clone 仓库二、编译运行官方demo三、发布Shadow到我们本地仓库3.1、安装Nexus 3.x版本3.2、修改发布配置3.3、发布仓库3.4、引用仓库包 四、编写我们自己的代码4.1、新建项目导入maven等共同配置4.1.1、导入buildScript4.1.…

NsPack3.x脱壳手记

发现是NsPack3.x的壳 使用ESP守恒快速脱壳 F9遇到popfd后下面的jmp就是通往OEP了 打开LordPE准备转储映像, 首先调整下ImageSize, 接着dump full 接着不要退出目前的调试, 打开Scylla修复IAT, 把OEP的VA地址输入到OEP处, 接着按照如下图所示步骤 完成后如下, 但NsPack3.x…

uni-app、H5实现瀑布流效果封装,列可以自定义

文章目录 前言一、效果二、使用代码三、核心代码总结前言 最近做项目需要实现uni-app、H5实现瀑布流效果封装,网上搜索有很多的例子,但是代码都是不够完整的,下面来封装一个uni-app、H5都能用的代码。在小程序中,一个个item渲染可能出现问题,也通过加锁来解决问题。 一、…