用go封装和实现扫码登录
用go封装和实现扫码登录
本篇为用go设计开发一个自己的轻量级登录库/框架吧 - 秋玻 - 博客园 (cnblogs.com)的扫码登录业务篇,会讲讲扫码登录的实现,给库/框架增加新的功能,最后说明使用方法
Github:https://github.com/weloe/token-go
扫码登录流程
首先我们需要知道扫码登录流程
- 打开登录页面,展示一个二维码,同时轮询二维码状态(web)
- 打开APP扫描该二维码后,APP显示确认、取消按钮(app)
- 登录页面展示被扫描的用户头像等信息(web)
- 用户在APP上点击确认登录(app)
- 登录页面从轮询二维码状态得知用户已确认登录,并获取到登录凭证(web)
- 页面登录成功,并进入主应用程序页面(web)
我们可以知道登录的二维码有一下几种状态:
- 等待扫码
- 已扫码,等待用户确认
- 已扫码,用户同意授权
- 已扫码,用户取消授权
- 已过期
而我们扫码的客户端(一般是手机App)可以修改二维码的状态,
- 确认已扫码
- 同意授权
- 取消授权
实现思路
我们封装的主要是二维码的状态维护,不包括生成二维码,二维码的生成交由使用者来实现。
而二维码的状态的常用的几个方法如下。
// QRCode api
// 初始化二维码状态
CreateQRCodeState(QRCodeId string, timeout int64) error
// 获取二维码剩余时间
GetQRCodeTimeout(QRCodeId string) int64
// 获取二维码信息
GetQRCode(QRCodeId string) *model.QRCode
// 获取二维码状态
GetQRCodeState(QRCodeId string) model.QRCodeState
// 确认已扫码
Scanned(QRCodeId string, loginId string) (string, error)
// 同意授权
ConfirmAuth(QRCodeTempToken string) error
// 取消授权
CancelAuth(QRCodeTempToken string) error
QRCodeId用于我们作为二维码状态的唯一标识。
在创建二维码时我们要传入QRCodeId以及timeout来设定二维码的超时时间,毕竟二维码总不能永久使用。
确认已扫码当然前提是在登录状态才能确认,因此我们用loginId作为参数用来跟QRCodeId来绑定。
对于同意授权和取消授权我们使用确认扫码的api返回的临时Token去进行操作。
而对信息的存储和获取则是使用框架内部的Adapter去获取。
代码实现
二维码状态和信息
首先我们要先设定一下二维码状态
等待扫码——1
已扫码,等待用户确认——2
已扫码,用户同意授权——3
已扫码,用户取消授权——4
已过期——5
package model
type QRCodeState int
// QRCode State
const (
WaitScan QRCodeState = 1
WaitAuth QRCodeState = 2
ConfirmAuth QRCodeState = 3
CancelAuth QRCodeState = 4
Expired QRCodeState = 5
)
维护二维码需要的信息,也就是二维码的唯一id,二维码当前状态,二维码对于的用户唯一id
type QRCode struct {
id string
State QRCodeState
LoginId string
}
func NewQRCode(id string) *QRCode {
return &QRCode{id: id, State: WaitScan}
}
初始化二维码状态
在APP扫码前我们要先创建一个二维码状态,设置为WaitScan,也就是1。而创建二维码信息,也就是使用我们框架内部的Adapter接口来存储
func (e *Enforcer) CreateQRCodeState(QRCodeId string, timeout int64) error {
return e.createQRCode(QRCodeId, timeout)
}
func (e *Enforcer) createQRCode(id string, timeout int64) error {
return e.adapter.Set(e.spliceQRCodeKey(id), model.NewQRCode(id), timeout)
}
e.spliceQRCodeKey是对存储的key的拼接方法。
获取二维码的剩余时间
通过QRCodeId使用我们的Adapter去获取
func (e *Enforcer) GetQRCodeTimeout(QRCodeId string) int64 {
return e.getQRCodeTimeout(QRCodeId)
}
func (e *Enforcer) getQRCodeTimeout(id string) int64 {
return e.adapter.GetTimeout(e.spliceQRCodeKey(id))
}
获取二维码信息
使用Adapter获取
func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode {
return e.getQRCode(QRCodeId)
}
获取二维码状态
同样使用Adapter获取
// GetQRCodeState
// WaitScan = 1
// WaitAuth = 2
// ConfirmAuth = 3
// CancelAuth = 4
// Expired = 5
func (e *Enforcer) GetQRCodeState(QRCodeId string) model.QRCodeState {
qrCode := e.getQRCode(QRCodeId)
if qrCode == nil {
return model.Expired
}
return qrCode.State
}
删除二维码信息
func (e *Enforcer) DeleteQRCode(QRCodeId string) error {
return e.deleteQRCode(QRCodeId)
}
func (e *Enforcer) deleteQRCode(id string) error {
return e.adapter.Delete(e.spliceQRCodeKey(id))
}
确认扫码
确认扫码要先判断二维码是否存在,接着校验二维码的状态是否是等待扫描WaitScan也就是1。校验完之后绑定用户唯一loginId,最后创建一个value值为QRCodeId的临时token返回。这个临时token用于同意授权和取消授权。
// Scanned update state to constant.WaitAuth, return tempToken
func (e *Enforcer) Scanned(QRCodeId string, loginId string) (string, error) {
qrCode := e.getQRCode(QRCodeId)
if qrCode == nil {
return "", fmt.Errorf("QRCode doesn't exist")
}
if qrCode.State != model.WaitScan {
return "", fmt.Errorf("QRCode state error: unexpected state value %v, want is %v", qrCode.State, model.WaitScan)
}
qrCode.State = model.WaitAuth
qrCode.LoginId = loginId
err := e.updateQRCode(QRCodeId, qrCode)
if err != nil {
return "", err
}
tempToken, err := e.CreateTempToken(e.config.TokenStyle, "qrCode", QRCodeId, e.config.Timeout)
if err != nil {
return "", err
}
return tempToken, nil
}
同意授权
同意授权要使用我们在确认扫码的时候返回的临时token,首先我们要校验这个临时token,这个ParseTempToken方法就是校验临时token,获取token对应的值的接口。在校验token后获取到QRCodeId,再去校验QRCodeId对应的状态,应该要是WaitAuth等待授权,也就是2。最后就是修改二维码状态为ConfirmAuth也就3。当然不能忘记删除临时token。
// ConfirmAuth update state to constant.ConfirmAuth
func (e *Enforcer) ConfirmAuth(tempToken string) error {
qrCodeId := e.ParseTempToken("qrCode", tempToken)
if qrCodeId == "" {
return fmt.Errorf("confirm failed, tempToken error: %v", tempToken)
}
qrCode, err := e.getAndCheckQRCodeState(qrCodeId, model.WaitAuth)
if err != nil {
return err
}
qrCode.State = model.ConfirmAuth
err = e.updateQRCode(qrCodeId, qrCode)
if err != nil {
return err
}
err = e.DeleteTempToken("qrCode", tempToken)
if err != nil {
return err
}
return err
}
取消授权
取消授权也要使用我们在确认扫码的时候返回的临时token,首先我们要校验这个临时token,这个ParseTempToken方法就是校验临时token的方法,通过这个方法获取到token对应的QRCodeId值。在校验token后获取到QRCodeId,再去校验QRCodeId对应的状态,应该要是WaitAuth等待授权,也就是2。最后就是修改二维码状态为CancelAuth也就4。同样不能忘记删除临时token。
// CancelAuth update state to constant.CancelAuth
func (e *Enforcer) CancelAuth(tempToken string) error {
qrCodeId := e.ParseTempToken("qrCode", tempToken)
if qrCodeId == "" {
return fmt.Errorf("confirm failed, tempToken error: %v", tempToken)
}
qrCode, err := e.getAndCheckQRCodeState(qrCodeId, model.WaitAuth)
if err != nil {
return err
}
qrCode.State = model.CancelAuth
err = e.updateQRCode(qrCodeId, qrCode)
if err != nil {
return err
}
err = e.DeleteTempToken("qrCode", tempToken)
if err != nil {
return err
}
return err
}
测试
func TestEnforcer_ConfirmQRCode(t *testing.T) {
enforcer, _ := NewTestEnforcer(t)
// in APP
loginId := "1"
token, err := enforcer.LoginById(loginId)
if err != nil {
t.Fatalf("Login failed: %v", err)
}
t.Logf("login token: %v", token)
qrCodeId := "q1"
err = enforcer.CreateQRCodeState(qrCodeId, -1)
if err != nil {
t.Fatalf("CreateQRCodeState() failed: %v", err)
}
t.Logf("After CreateQRCodeState(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
loginIdByToken, err := enforcer.GetLoginIdByToken(token)
if err != nil {
t.Fatalf("GetLoginIdByToken() failed: %v", err)
}
tempToken, err := enforcer.Scanned(qrCodeId, loginIdByToken)
if err != nil {
t.Fatalf("Scanned() failed: %v", err)
}
if state := enforcer.GetQRCodeState(qrCodeId); state != model.WaitAuth {
t.Fatalf("After Scanned(), QRCode should be %v", model.WaitAuth)
}
t.Logf("After Scanned(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
t.Logf("tempToken: %v", tempToken)
err = enforcer.ConfirmAuth(tempToken)
if err != nil {
t.Fatalf("ConfirmAuth() failed: %v", err)
}
if state := enforcer.GetQRCodeState(qrCodeId); state != model.ConfirmAuth {
t.Fatalf("After ConfirmAuth(), QRCode should be %v", model.ConfirmAuth)
}
t.Logf("After ConfirmAuth(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
if enforcer.GetQRCodeState(qrCodeId) == model.ConfirmAuth {
loginId := enforcer.getQRCode(qrCodeId).LoginId
t.Logf("id: [%v] QRCode login successfully.", loginId)
}
}
如何使用
https://github.com/weloe/token-go/blob/master/examples/qrcode/qrcode-server.go
安装token-go, go get github.com/weloe/token-go
package main
import (
"fmt"
tokenGo "github.com/weloe/token-go"
"github.com/weloe/token-go/model"
"log"
"net/http"
)
var enforcer *tokenGo.Enforcer
func main() {
var err error
// use default adapter
adapter := tokenGo.NewDefaultAdapter()
enforcer, err = tokenGo.NewEnforcer(adapter)
// enable logger
enforcer.EnableLog()
if err != nil {
log.Fatal(err)
}
http.HandleFunc("/qrcode/create", create)
http.HandleFunc("/qrcode/scanned", scanned)
http.HandleFunc("/qrcode/confirmAuth", confirmAuth)
http.HandleFunc("/qrcode/cancelAuth", cancelAuth)
http.HandleFunc("/qrcode/getState", getState)
log.Fatal(http.ListenAndServe(":8081", nil))
}
func create(w http.ResponseWriter, request *http.Request) {
// you should implement generate QR code method, returns QRCodeId to CreateQRCodeState
// called generate QR code, returns QRCodeId to CreateQRCodeState
//
QRCodeId := "generatedQRCodeId"
err := enforcer.CreateQRCodeState(QRCodeId, 50000)
if err != nil {
fmt.Fprintf(w, "CreateQRCodeState() failed: %v", err)
return
}
fmt.Fprintf(w, "QRCodeId = %v", QRCodeId)
}
func scanned(w http.ResponseWriter, req *http.Request) {
loginId, err := enforcer.GetLoginId(tokenGo.NewHttpContext(req, w))
if err != nil {
fmt.Fprintf(w, "GetLoginId() failed: %v", err)
return
}
QRCodeId := req.URL.Query().Get("QRCodeId")
tempToken, err := enforcer.Scanned(QRCodeId, loginId)
if err != nil {
fmt.Fprintf(w, "Scanned() failed: %v", err)
return
}
fmt.Fprintf(w, "tempToken = %v", tempToken)
}
func getState(w http.ResponseWriter, req *http.Request) {
QRCodeId := req.URL.Query().Get("QRCodeId")
state := enforcer.GetQRCodeState(QRCodeId)
if state == model.ConfirmAuth {
qrCode := enforcer.GetQRCode(QRCodeId)
if qrCode == nil {
fmt.Fprintf(w, "login error. state = %v, code is nil", state)
return
}
loginId := qrCode.LoginId
token, err := enforcer.LoginById(loginId)
if err != nil {
fmt.Fprintf(w, "Login error: %s\n", err)
}
fmt.Fprintf(w, "%v login success. state = %v, token = %v", loginId, state, token)
return
} else if state == model.CancelAuth {
fmt.Fprintf(w, "QRCodeId be cancelled: %v", QRCodeId)
return
}
fmt.Fprintf(w, "state = %v", state)
}
func cancelAuth(w http.ResponseWriter, req *http.Request) {
tempToken := req.URL.Query().Get("tempToken")
err := enforcer.CancelAuth(tempToken)
if err != nil {
fmt.Fprintf(w, "CancelAuth() failed: %v", err)
return
}
fmt.Fprint(w, "ConfirmAuth() success")
}
func confirmAuth(w http.ResponseWriter, req *http.Request) {
tempToken := req.URL.Query().Get("tempToken")
err := enforcer.ConfirmAuth(tempToken)
if err != nil {
fmt.Fprintf(w, "ConfirmAuth() failed: %v", err)
return
}
fmt.Fprint(w, "ConfirmAuth() success")
}
从最开始的流程和测试方法中也可以知道
首先我们需要在Web端(需要扫码登录的客户端)生成二维码后携带参数二维码id请求后端/qrcode/create,后端调用生成二维码的方法(需要自己实现),然后调用enforcer.CreateQRCodeState()方法初始化二维码状态。
从APP端扫码二维码,请求后端/qrcode/scanned,后端先校验一下APP传来的token判断(使用框架的enforcer.isLoginByToken()方法来判断)是否在登录态,使用enforcer.GetLoginId()获取对应的loginId,再调用enforcer.Scanned()方法。之后返回临时token。
APP端收到临时token后,选择同意或者取消授权,也就是传临时token到后端/qrcode/confirmAuth或者/qrcode/cancelAuth,后端调用enforcer.ConfirmAuth()或者enforcer.CancelAuth()方法同意或者取消授权。
而Web端在初始化二维码状态后要持续请求后端/qrcode/getState,后端调用GetQRCodeState方法去获取二维码状态,如果二维码状态为超时也就是Expired前端就删除二维码信息,提示二维码过期,重新生成二维码,如果获取到状态等于确认授权ConfirmAuth就进行登录操作enforcer.LoginById(),返回登录凭证token。
用go封装和实现扫码登录的更多相关文章
- thymeltesys-基于Spring Boot Oauth2的扫码登录框架
thymeltesys thymelte是一个基于Spring Boot Oauth2的扫码登录框架,使用PostgreSQL存储数据,之后会慢慢支持其他关系型数据库.即使你不使用整个框架,只使用其中 ...
- 【JavaWeb开发】初步实现网站应用钉钉扫码登录
http://blog.csdn.net/baofeidyz/article/details/59059379 版权声明:转载请注明我的个人微信平台 暴沸 目录(?)[+] 写在前面:如果你还不知道钉 ...
- 微信开放平台PC端扫码登录功能个人总结
最近公司给我安排一个微信登录的功能,需求是这样的: 1.登录授权 点击二维码图标后,登录界面切换为如下样式(二维码),微信扫描二维码并授权,即可成功登录: 若当前账号未绑定微信账号,扫描后提示“ ...
- php微信开放平台--第三方网页微信扫码登录(OAuth2.0)
第一.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提 ...
- Java 语言实现简易版扫码登录
基本介绍 相信大家对二维码都不陌生,生活中到处充斥着扫码登录的场景,如登录网页版微信.支付宝等.最近学习了一下扫码登录的原理,感觉蛮有趣的,于是自己实现了一个简易版扫码登录的 Demo,以此记录一下学 ...
- Spring Security整合企业微信的扫码登录,企微的API震惊到我了
本文代码: https://gitee.com/felord/spring-security-oauth2-tutorial/tree/wwopen/ 现在很多企业都接入了企业微信,作为私域社群工具, ...
- Web应用多账号系统设计及微信扫码登录实现
Web应用多账号系统设计及微信扫码登录实现 1 前言概述 公司对功能测试,性能测试,安全测试等等都做了比较好的自动化后,急需要一个MIS系统来统一管理这些结果及报表. 此MIS系统特点如下: 仅内 ...
- C#开发微信门户及应用(45)--微信扫码登录
在前面随笔<C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理>介绍了基于微信开放平台接口实现的微信扫码直接登录的过程.本篇介绍对扫码登录的一些改进和处理,以便更方便应用在实 ...
- C#开发微信门户及应用(41)--基于微信开放平台的扫码登录处理
在现今很多网站里面,都使用了微信开放平台的扫码登录认证处理,这样做相当于把身份认证交给较为权威的第三方进行认证,在应用网站里面可以不需要存储用户的密码了.本篇介绍如何基于微信开放平台的扫码进行网站的登 ...
- 微信开放平台开发——网页微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
随机推荐
- 基于thumbnailator封装图片处理工具类,实现图片的裁剪、压缩、图片水印、文字水印、多行文字水印等功能
目录 一.前言 二.工具类的依赖和简单介绍 1.添加依赖 2.简单的使用 3.加载需要处理的图片 4.添加图片处理规则 4.1 Builder的方式 4.2 使用规则工厂的方式 5.输出处理后的图片 ...
- 【WebRtc】获取音视频数据
首页截图 获取音视频 关键Code 获取摄像头数据 /** * 获取流数据 */ openUserMeida() { var that = this // 判断是否支持获取媒体数据 if (!navi ...
- 【转载】Linux虚拟化KVM-Qemu分析(四)之CPU虚拟化(2)
原文信息: 作者:LoyenWang 出处:https://www.cnblogs.com/LoyenWang/ 公众号:LoyenWang 版权:本文版权归作者和博客园共有 转载:欢迎转载,但未经作 ...
- 面试官:一个 SpringBoot 项目能处理多少请求?(小心有坑)
你好呀,我是歪歪. 这篇文章带大家盘一个读者遇到的面试题哈. 根据读者转述,面试官的原问题就是:一个 SpringBoot 项目能同时处理多少请求? 不知道你听到这个问题之后的第一反应是什么. 我大概 ...
- typedef函数的使用
typedef int INT; //相当于给int起了一个别名INT typedef struct Student { int sid; char name[100]; char sex; }ST; ...
- Llama2开源大模型的新篇章以及在阿里云的实践
Llama一直被誉为AI社区中最强大的开源大模型.然而,由于开源协议的限制,它一直不能被免费用于商业用途.然而,这一切在7月19日发生了改变,当Meta终于发布了大家期待已久的免费商用版本Llama2 ...
- 打造原生 WebGL 2D 引擎:一场创意与技术的融合
打造原生 WebGL 2D 引擎:一场创意与技术的融合 1.引言 在当今数字化时代,网页的功能越来越丰富,已经远远超越了传统的文本和图片呈现.我们生活在一个充满交互性和视觉魅力的网络世界.每天都会遇到 ...
- quarkus实战之六:配置
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<quarkus实战>系列 ...
- Python数据可视化-折线图
Python数据可视化-折线图 一.JSON数据格式 1.1 什么是json JSON是一种轻量级的数据交互格式.可以按照JSON指定的格式去组织和封装数据 JSON本质上是一个带有特定格式的字符串 ...
- mysql注释的方法
单行注释:"#", "--", 多行注释:/**/ 参考链接:https://www.cnblogs.com/JiangLe/articles/6897403. ...