微信扫码登录实现原理

本贴最后更新于 3161 天前,其中的信息可能已经事过境迁
我最近的很多工作围绕着微信公众号的开发进行的。对微信公众号开发的很多细节都比较清楚。扫码登录也基本是首选的登录方式。这里我就记录一下我们的微信扫码登录时怎么实现的。 我们是用websocket来实现的。大体思路就是 >* 前端请求登录二维码的时候传token参数,后端保存token到数据库 >* 手机扫码。后台判断扫码事件的参数是否合法,然后通过websocket把验证结果发送给客户端 >* 客户端如果收到成功消息。则向服务端发送一条登录成功的确认消息。 >* 服务端收到之后,进行用户登录状态的处理,session的保存,把处理结果返回给客户端 >* 客户端再次收到成功标志之后,做登录跳转。标志着登录成功。 所有异常结果都由前端提示操作失败。 ##1. 进入登录页面,前端向后台请求二维码 在扫码登录页面,前端向后台发个Ajax请求,请求扫描登录二维码。这里需要一个传一个请求参数token,因为后面要用它进行验证。 token可用时间戳+随机数获取 ``` var token = '' + new Date().getTime() + '_' + Math.floor(Math.random() * 1000); ``` 后台接收到token后,要保存到数据库。后面验证需要。二维码的接口写法当然有很多种。这是我的做法(方法细节就不列出来了),groovy代码: ``` // 场景二维码 当用户未关注时弹出 handler.get('/ih/bpv2/get-login-qrcode'){req,resp-> String token = req.params.token byte[] b = BpAddService.getFocusQr(token) if(!b){ log.info('ih bp qr generate error') resp.statusCode = 404 resp.end '' return } AuthService.addToken(token) resp.headers.set('Content-type', 'image/jpg') resp.end(new Buffer(b)) } ``` 返回的直接是一张图片,所以替换img src即可。 前端调用二维码的方式为 ``` $('#qr').attr('src', '/ih/bpv2/get-login-qrcode?token=' + token); ``` ##2. 通过websocket向后台接口发送token 用sockjs实现,前端写法为 ``` sock = new SockJS(sockServerUrl); sock.onopen = function(){ console.log('open'); send({action: 'wx-auth-login-mapping-token-4-bpv2', token: tokenLast}); }; ``` 后端接口接收到之后的处理方式就是记录登录方的`ip`,`token`和`sockId`信息,放在缓存`Redis`中 详细代码如下: ``` // client id binding with token,现在是一台机器 handlerSockjs.add('wx-auth-login-mapping-token-4-bpv2'){sock, json -> if(!json.token) return [flag: false, msg: 'no token'] String ipThis = IPUtils.getFirstNoLoopbackAddress() String sp = '--' def ins = JedisStore.getInstance(config.redisHost) ins.set('WX_AUTH_TOKEN_SOCKID', json.token, ipThis + sp + sock.writeHandlerID) [flag: true] } ``` ##3. 手机扫码登录 这个二维码是微信的场景二维码,手机扫码事件会被微信接收到,然后推送到我们的服务器。推送过来的参数就有用户的openid,我们再从场景二位码的场景值中用正则匹配出token。拿这个token,和数据库的对比,看是不是合法的。如已经登录过。伪造token等情况。不合法的时候直接返回错误信息。如果合法则用websocket向客户端发送一条消息,告诉客户端扫码验证成功了。客户端收到之后就是扫码真正成功的标志。客户端再需要向服务端发送一条消息确认成功。准备去登录成功的页面。服务端再做一次判断。添加用户登录的cookie等信息。返回给客户端成功消息的时候,客户端才算是真正成功。做跳转。 后端对微信扫码登录的处理逻辑如下: ``` // 扫描二维码,发送模板消息和生成名片的连接 WxMessager.inst.add('event', /ih\-bpv2\-focus\-qrcode\-.+/){Map<String, String> info -> String openid = info.FromUserName Map user = WxUserLocal.getUserByOpenid(openid) if(!user) return // 场景值 String pre = 'ih-bpv2-focus-qrcode-' int start = pre.size() String token = info.EventKey[start..-1] log.info('ih bp login token - ' + openid + ' - ' + token) // check token def tokenRow = AuthService.getTokenRow(token) if(!tokenRow || tokenRow.status != 0){ return } int tokenRowId = tokenRow.id AuthService.updateTokenRow(tokenRowId, Constant.STATUS_YES, openid) // broadcast def ins = JedisStore.getInstance(config.redisHost) String ipAndSockid = ins.get('WX_AUTH_TOKEN_SOCKID', token) if(!ipAndSockid){ log.info('broadcast not found sock id - ' + token) return } log.info('broadcast found sock id - ' + ipAndSockid) String sp = '--' String[] arr = ipAndSockid.split(sp) String targetIp = arr[0] String sockid = arr[1] String ipThis = IPUtils.getFirstNoLoopbackAddress() if(targetIp == ipThis){ handlerSockjs.send(sockid, [action: 'loginok']) }else{ int port = 80 String url = 'http://' + targetIp + ':' + port + '/ih/bpv2/front/broadcast' log.info('front user login redirect - ' + url) Map params = [:] params.sockid = sockid String body = JSON.toJSONString(params) try{ def request = HttpRequest.post(url).connectTimeout(1000 * 2).send(body) String str = request.body() log.info('front user login redirect result ' + str) if(!str){ log.info('front user login redirect by http return blank ' + sockid) }else{ def resultSend = JSON.parse(str) if(resultSend && resultSend.flag){ log.info('front user login redirect by http return ok ' + sockid) }else{ log.info('front user login redirect by http return error ' + sockid) } } }catch(e){ log.error('front user login redirect error - ' + sockid, e) } } String inner = '' String loginTime = new Date().format('yyyy-MM-dd HH:mm:ss') // 您与${loginTime}登录了xxxx平台 Map cardSaved = new CardRefactorService().getCardInfoByOpenid(openid) boolean isCardFill = cardSaved && cardSaved.name if(!isCardFill) { String redirectUrl = '/ih/comm/index?hash=/card-edit' String url = WxApi.getLoginRedirectUrl(redirectUrl) inner = "<a href=\"${url}\">点击此处生成你的名片,方便BP审核通过后联系你</a>" } String c = """欢迎使用xxxxx功能 ${inner}""" [MsgType: 'text', Content: c] } ``` 前端处理逻辑: ``` sock.onmessage = function(e){ console.log(e.data); if(!e.data) return; var obj = JSON.parse(e.data); if(obj && 'reject' == obj.action){ console.log('登陆失败'); return; } if(obj && 'loginok' == obj.action){ $.get('/ih/bpv2/front/login/done?token=' + tokenLast, function(data){ var errs = { 'token-invalid': '令牌时效' }; if(data.error){ alert(errs[data.error]); return; } document.location.href = '/bp_20/detail-list.html'; /* if(data.isAdmin){ document.location.href = '/bp/manage.html'; }else{ }*/ }); return; } }; ``` 后台最后一次验证的逻辑 ``` // 用websocket登陆成功后 handler.get('/ih/bpv2/front/login/done'){req, resp -> String token = req.params.token if(!token){ resp.json([error: 'token-invalid']) return } def tokenRow = AuthService.getTokenRow(token) if(!tokenRow || tokenRow.status != 1){ resp.json([error: 'token-invalid']) return } String openid = tokenRow.openid resp.addCookie([name: 'user-login-openid', value: openid, path: '/']) resp.addCookie([name: 'user-login-id', value: '', path: '/']) BpAddService.addRole([openid: openid, roleId: BpConstant.BP_USER_ROLE_GENERAL_USER]) resp.json([flag: true, info: 'login success']) } ``` 至此,所有流程走完。
  • 微信

    腾讯公司 2011 年 1 月 21 日推出的一款手机通讯软件。用户可以通过摇一摇、搜索号码、扫描二维码等添加好友和关注公众平台,同时可以将自己看到的精彩内容分享到微信朋友圈。

    132 引用 • 796 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
guobing
会当凌绝顶,一览众山小

推荐标签 标签

  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 3 关注
  • Access
    1 引用 • 3 回帖 • 2 关注
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    90 引用 • 59 回帖 • 7 关注
  • Bootstrap

    Bootstrap 是 Twitter 推出的一个用于前端开发的开源工具包。它由 Twitter 的设计师 Mark Otto 和 Jacob Thornton 合作开发,是一个 CSS / HTML 框架。

    18 引用 • 33 回帖 • 650 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    85 引用 • 165 回帖 • 1 关注
  • Shell

    Shell 脚本与 Windows/Dos 下的批处理相似,也就是用各类命令预先放入到一个文件中,方便一次性执行的一个程序文件,主要是方便管理员进行设置或者管理用的。但是它比 Windows 下的批处理更强大,比用其他编程程序编辑的程序效率更高,因为它使用了 Linux/Unix 下的命令。

    124 引用 • 74 回帖
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    99 引用 • 367 回帖
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 177 关注
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    43 引用 • 208 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    6 引用 • 26 回帖 • 548 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 702 关注
  • 反馈

    Communication channel for makers and users.

    126 引用 • 930 回帖 • 270 关注
  • Office

    Office 现已更名为 Microsoft 365. Microsoft 365 将高级 Office 应用(如 Word、Excel 和 PowerPoint)与 1 TB 的 OneDrive 云存储空间、高级安全性等结合在一起,可帮助你在任何设备上完成操作。

    5 引用 • 34 回帖 • 1 关注
  • Laravel

    Laravel 是一套简洁、优雅的 PHP Web 开发框架。它采用 MVC 设计,是一款崇尚开发效率的全栈框架。

    20 引用 • 23 回帖 • 740 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    10 引用 • 76 回帖
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 677 关注
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 20 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 391 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    181 引用 • 400 回帖 • 4 关注
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖 • 2 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    76 引用 • 1742 回帖
  • CodeMirror
    2 引用 • 17 回帖 • 158 关注
  • WordPress

    WordPress 是一个使用 PHP 语言开发的博客平台,用户可以在支持 PHP 和 MySQL 数据库的服务器上架设自己的博客。也可以把 WordPress 当作一个内容管理系统(CMS)来使用。WordPress 是一个免费的开源项目,在 GNU 通用公共许可证(GPLv2)下授权发布。

    66 引用 • 114 回帖 • 196 关注
  • 尊园地产

    昆明尊园房地产经纪有限公司,即:Kunming Zunyuan Property Agency Company Limited(简称“尊园地产”)于 2007 年 6 月开始筹备,2007 年 8 月 18 日正式成立,注册资本 200 万元,公司性质为股份经纪有限公司,主营业务为:代租、代售、代办产权过户、办理银行按揭、担保、抵押、评估等。

    1 引用 • 22 回帖 • 782 关注
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    21 引用 • 204 回帖
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 643 关注