用go封装和实现扫码登录

1|0用go封装和实现扫码登录

本篇为用go设计开发一个自己的轻量级登录库/框架吧 - 秋玻 - 博客园 (cnblogs.com)的扫码登录业务篇,会讲讲扫码登录的实现,给库/框架增加新的功能,最后说明使用方法

Github:https://github.com/weloe/token-go

1|1扫码登录流程

首先我们需要知道扫码登录流程

  1. 打开登录页面,展示一个二维码,同时轮询二维码状态(web)
  2. 打开APP扫描该二维码后,APP显示确认、取消按钮(app)
  3. 登录页面展示被扫描的用户头像等信息(web)
  4. 用户在APP上点击确认登录(app)
  5. 登录页面从轮询二维码状态得知用户已确认登录,并获取到登录凭证(web)
  6. 页面登录成功,并进入主应用程序页面(web)

我们可以知道登录的二维码有一下几种状态:

  1. 等待扫码
  2. 已扫码,等待用户确认
  3. 已扫码,用户同意授权
  4. 已扫码,用户取消授权
  5. 已过期

而我们扫码的客户端(一般是手机App)可以修改二维码的状态,

  1. 确认已扫码
  2. 同意授权
  3. 取消授权

1|2实现思路

我们封装的主要是二维码的状态维护,不包括生成二维码,二维码的生成交由使用者来实现。

而二维码的状态的常用的几个方法如下。

// 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|3代码实现

1|0二维码状态和信息

首先我们要先设定一下二维码状态

等待扫码——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} }

1|0初始化二维码状态

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L229

在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的拼接方法。

1|0获取二维码的剩余时间

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L319

通过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)) }

1|0获取二维码信息

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L301

使用Adapter获取

func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode { return e.getQRCode(QRCodeId) }

1|0获取二维码状态

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L311

同样使用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 }

1|0删除二维码信息

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L323

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

1|0确认扫码

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L234

确认扫码要先判断二维码是否存在,接着校验二维码的状态是否是等待扫描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 }

1|0同意授权

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L257

同意授权要使用我们在确认扫码的时候返回的临时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 }

1|0取消授权

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L280

取消授权也要使用我们在确认扫码的时候返回的临时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 }

1|4测试

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

1|5如何使用

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。


__EOF__

本文作者秋玻
本文链接https://www.cnblogs.com/weloe/p/17770329.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   秋玻  阅读(496)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 我与微信审核的“相爱相杀”看个人小程序副业
点击右上角即可分享
微信分享提示