用go封装和实现扫码登录

打印 上一主题 下一主题

主题 935|帖子 935|积分 2805

用go封装和实现扫码登录

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

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

  • 打开登录页面,展示一个二维码,同时轮询二维码状态(web)
  • 打开APP扫描该二维码后,APP显示确认、取消按钮(app)
  • 登录页面展示被扫描的用户头像等信息(web)
  • 用户在APP上点击确认登录(app)
  • 登录页面从轮询二维码状态得知用户已确认登录,并获取到登录凭证(web)
  • 页面登录成功,并进入主应用程序页面(web)
我们可以知道登录的二维码有一下几种状态:

  • 等待扫码
  • 已扫码,等待用户确认
  • 已扫码,用户同意授权
  • 已扫码,用户取消授权
  • 已过期
而我们扫码的客户端(一般是手机App)可以修改二维码的状态,

  • 确认已扫码
  • 同意授权
  • 取消授权
实现思路

我们封装的主要是二维码的状态维护,不包括生成二维码,二维码的生成交由使用者来实现。
而二维码的状态的常用的几个方法如下。
  1. // QRCode api
  2. // 初始化二维码状态
  3. CreateQRCodeState(QRCodeId string, timeout int64) error
  4. // 获取二维码剩余时间
  5. GetQRCodeTimeout(QRCodeId string) int64
  6. // 获取二维码信息
  7. GetQRCode(QRCodeId string) *model.QRCode
  8. // 获取二维码状态
  9. GetQRCodeState(QRCodeId string) model.QRCodeState
  10. // 确认已扫码
  11. Scanned(QRCodeId string, loginId string) (string, error)
  12. // 同意授权
  13. ConfirmAuth(QRCodeTempToken string) error
  14. // 取消授权
  15. CancelAuth(QRCodeTempToken string) error
复制代码
QRCodeId用于我们作为二维码状态的唯一标识。
在创建二维码时我们要传入QRCodeId以及timeout来设定二维码的超时时间,毕竟二维码总不能永久使用。
确认已扫码当然前提是在登录状态才能确认,因此我们用loginId作为参数用来跟QRCodeId来绑定。
对于同意授权和取消授权我们使用确认扫码的api返回的临时Token去进行操作。
而对信息的存储和获取则是使用框架内部的Adapter去获取。
代码实现

二维码状态和信息

首先我们要先设定一下二维码状态
等待扫码——1
已扫码,等待用户确认——2
已扫码,用户同意授权——3
已扫码,用户取消授权——4
已过期——5
  1. package model
  2. type QRCodeState int
  3. // QRCode State
  4. const (
  5.         WaitScan    QRCodeState = 1
  6.         WaitAuth    QRCodeState = 2
  7.         ConfirmAuth QRCodeState = 3
  8.         CancelAuth  QRCodeState = 4
  9.         Expired     QRCodeState = 5
  10. )
复制代码
维护二维码需要的信息,也就是二维码的唯一id,二维码当前状态,二维码对于的用户唯一id
  1. type QRCode struct {
  2.         id      string
  3.         State   QRCodeState
  4.         LoginId string
  5. }
  6. func NewQRCode(id string) *QRCode {
  7.         return &QRCode{id: id, State: WaitScan}
  8. }
复制代码
初始化二维码状态

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L229
在APP扫码前我们要先创建一个二维码状态,设置为WaitScan,也就是1。而创建二维码信息,也就是使用我们框架内部的Adapter接口来存储
  1. func (e *Enforcer) CreateQRCodeState(QRCodeId string, timeout int64) error {
  2.         return e.createQRCode(QRCodeId, timeout)
  3. }
复制代码
  1. func (e *Enforcer) createQRCode(id string, timeout int64) error {
  2.         return e.adapter.Set(e.spliceQRCodeKey(id), model.NewQRCode(id), timeout)
  3. }
复制代码
e.spliceQRCodeKey是对存储的key的拼接方法。
获取二维码的剩余时间

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L319
通过QRCodeId使用我们的Adapter去获取
  1. func (e *Enforcer) GetQRCodeTimeout(QRCodeId string) int64 {
  2.     return e.getQRCodeTimeout(QRCodeId)
  3. }
复制代码
  1. func (e *Enforcer) getQRCodeTimeout(id string) int64 {
  2.         return e.adapter.GetTimeout(e.spliceQRCodeKey(id))
  3. }
复制代码
获取二维码信息

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L301
使用Adapter获取
  1. func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode {
  2.         return e.getQRCode(QRCodeId)
  3. }
复制代码
获取二维码状态

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L311
同样使用Adapter获取
  1. // GetQRCodeState
  2. //        WaitScan   = 1
  3. //        WaitAuth   = 2
  4. //        ConfirmAuth  = 3
  5. //        CancelAuth = 4
  6. //        Expired    = 5
  7. func (e *Enforcer) GetQRCodeState(QRCodeId string) model.QRCodeState {
  8.         qrCode := e.getQRCode(QRCodeId)
  9.         if qrCode == nil {
  10.                 return model.Expired
  11.         }
  12.         return qrCode.State
  13. }
复制代码
删除二维码信息

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L323
  1. func (e *Enforcer) DeleteQRCode(QRCodeId string) error {
  2.         return e.deleteQRCode(QRCodeId)
  3. }
复制代码
  1. func (e *Enforcer) deleteQRCode(id string) error {
  2.         return e.adapter.Delete(e.spliceQRCodeKey(id))
  3. }
复制代码
确认扫码

https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L234
确认扫码要先判断二维码是否存在,接着校验二维码的状态是否是等待扫描WaitScan也就是1。校验完之后绑定用户唯一loginId,最后创建一个value值为QRCodeId的临时token返回。这个临时token用于同意授权和取消授权。
  1. // Scanned update state to constant.WaitAuth, return tempToken
  2. func (e *Enforcer) Scanned(QRCodeId string, loginId string) (string, error) {
  3.         qrCode := e.getQRCode(QRCodeId)
  4.         if qrCode == nil {
  5.                 return "", fmt.Errorf("QRCode doesn't exist")
  6.         }
  7.         if qrCode.State != model.WaitScan {
  8.                 return "", fmt.Errorf("QRCode state error: unexpected state value %v, want is %v", qrCode.State, model.WaitScan)
  9.         }
  10.         qrCode.State = model.WaitAuth
  11.         qrCode.LoginId = loginId
  12.         err := e.updateQRCode(QRCodeId, qrCode)
  13.         if err != nil {
  14.                 return "", err
  15.         }
  16.         tempToken, err := e.CreateTempToken(e.config.TokenStyle, "qrCode", QRCodeId, e.config.Timeout)
  17.         if err != nil {
  18.                 return "", err
  19.         }
  20.         return tempToken, nil
  21. }
复制代码
同意授权

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。
  1. // ConfirmAuth update state to constant.ConfirmAuth
  2. func (e *Enforcer) ConfirmAuth(tempToken string) error {
  3.         qrCodeId := e.ParseTempToken("qrCode", tempToken)
  4.         if qrCodeId == "" {
  5.                 return fmt.Errorf("confirm failed, tempToken error: %v", tempToken)
  6.         }
  7.         qrCode, err := e.getAndCheckQRCodeState(qrCodeId, model.WaitAuth)
  8.         if err != nil {
  9.                 return err
  10.         }
  11.         qrCode.State = model.ConfirmAuth
  12.         err = e.updateQRCode(qrCodeId, qrCode)
  13.         if err != nil {
  14.                 return err
  15.         }
  16.         err = e.DeleteTempToken("qrCode", tempToken)
  17.         if err != nil {
  18.                 return err
  19.         }
  20.         return err
  21. }
复制代码
取消授权

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。
  1. // CancelAuth update state to constant.CancelAuth
  2. func (e *Enforcer) CancelAuth(tempToken string) error {
  3.         qrCodeId := e.ParseTempToken("qrCode", tempToken)
  4.         if qrCodeId == "" {
  5.                 return fmt.Errorf("confirm failed, tempToken error: %v", tempToken)
  6.         }
  7.         qrCode, err := e.getAndCheckQRCodeState(qrCodeId, model.WaitAuth)
  8.         if err != nil {
  9.                 return err
  10.         }
  11.         qrCode.State = model.CancelAuth
  12.         err = e.updateQRCode(qrCodeId, qrCode)
  13.         if err != nil {
  14.                 return err
  15.         }
  16.         err = e.DeleteTempToken("qrCode", tempToken)
  17.         if err != nil {
  18.                 return err
  19.         }
  20.         return err
  21. }
复制代码
测试
  1. func TestEnforcer_ConfirmQRCode(t *testing.T) {
  2.         enforcer, _ := NewTestEnforcer(t)
  3.         // in APP
  4.         loginId := "1"
  5.         token, err := enforcer.LoginById(loginId)
  6.         if err != nil {
  7.                 t.Fatalf("Login failed: %v", err)
  8.         }
  9.         t.Logf("login token: %v", token)
  10.         qrCodeId := "q1"
  11.         err = enforcer.CreateQRCodeState(qrCodeId, -1)
  12.         if err != nil {
  13.                 t.Fatalf("CreateQRCodeState() failed: %v", err)
  14.         }
  15.         t.Logf("After CreateQRCodeState(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
  16.         loginIdByToken, err := enforcer.GetLoginIdByToken(token)
  17.         if err != nil {
  18.                 t.Fatalf("GetLoginIdByToken() failed: %v", err)
  19.         }
  20.         tempToken, err := enforcer.Scanned(qrCodeId, loginIdByToken)
  21.         if err != nil {
  22.                 t.Fatalf("Scanned() failed: %v", err)
  23.         }
  24.         if state := enforcer.GetQRCodeState(qrCodeId); state != model.WaitAuth {
  25.                 t.Fatalf("After Scanned(), QRCode should be %v", model.WaitAuth)
  26.         }
  27.         t.Logf("After Scanned(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
  28.         t.Logf("tempToken: %v", tempToken)
  29.         err = enforcer.ConfirmAuth(tempToken)
  30.         if err != nil {
  31.                 t.Fatalf("ConfirmAuth() failed: %v", err)
  32.         }
  33.         if state := enforcer.GetQRCodeState(qrCodeId); state != model.ConfirmAuth {
  34.                 t.Fatalf("After ConfirmAuth(), QRCode should be %v", model.ConfirmAuth)
  35.         }
  36.         t.Logf("After ConfirmAuth(), current QRCode state: %v", enforcer.GetQRCodeState(qrCodeId))
  37.         if enforcer.GetQRCodeState(qrCodeId) == model.ConfirmAuth {
  38.                 loginId := enforcer.getQRCode(qrCodeId).LoginId
  39.                 t.Logf("id: [%v] QRCode login successfully.", loginId)
  40.         }
  41. }
复制代码
如何使用

https://github.com/weloe/token-go/blob/master/examples/qrcode/qrcode-server.go
安装token-go, go get github.com/weloe/token-go
  1. package main
  2. import (
  3.         "fmt"
  4.         tokenGo "github.com/weloe/token-go"
  5.         "github.com/weloe/token-go/model"
  6.         "log"
  7.         "net/http"
  8. )
  9. var enforcer *tokenGo.Enforcer
  10. func main() {
  11.         var err error
  12.         // use default adapter
  13.         adapter := tokenGo.NewDefaultAdapter()
  14.         enforcer, err = tokenGo.NewEnforcer(adapter)
  15.         // enable logger
  16.         enforcer.EnableLog()
  17.         if err != nil {
  18.                 log.Fatal(err)
  19.         }
  20.         http.HandleFunc("/qrcode/create", create)
  21.         http.HandleFunc("/qrcode/scanned", scanned)
  22.         http.HandleFunc("/qrcode/confirmAuth", confirmAuth)
  23.         http.HandleFunc("/qrcode/cancelAuth", cancelAuth)
  24.         http.HandleFunc("/qrcode/getState", getState)
  25.         log.Fatal(http.ListenAndServe(":8081", nil))
  26. }
  27. func create(w http.ResponseWriter, request *http.Request) {
  28.         // you should implement generate QR code method, returns QRCodeId to CreateQRCodeState
  29.         // called generate QR code, returns QRCodeId to CreateQRCodeState
  30.         //
  31.         QRCodeId := "generatedQRCodeId"
  32.         err := enforcer.CreateQRCodeState(QRCodeId, 50000)
  33.         if err != nil {
  34.                 fmt.Fprintf(w, "CreateQRCodeState() failed: %v", err)
  35.                 return
  36.         }
  37.         fmt.Fprintf(w, "QRCodeId = %v", QRCodeId)
  38. }
  39. func scanned(w http.ResponseWriter, req *http.Request) {
  40.         loginId, err := enforcer.GetLoginId(tokenGo.NewHttpContext(req, w))
  41.         if err != nil {
  42.                 fmt.Fprintf(w, "GetLoginId() failed: %v", err)
  43.                 return
  44.         }
  45.         QRCodeId := req.URL.Query().Get("QRCodeId")
  46.         tempToken, err := enforcer.Scanned(QRCodeId, loginId)
  47.         if err != nil {
  48.                 fmt.Fprintf(w, "Scanned() failed: %v", err)
  49.                 return
  50.         }
  51.         fmt.Fprintf(w, "tempToken = %v", tempToken)
  52. }
  53. func getState(w http.ResponseWriter, req *http.Request) {
  54.         QRCodeId := req.URL.Query().Get("QRCodeId")
  55.         state := enforcer.GetQRCodeState(QRCodeId)
  56.         if state == model.ConfirmAuth {
  57.                 qrCode := enforcer.GetQRCode(QRCodeId)
  58.                 if qrCode == nil {
  59.                         fmt.Fprintf(w, "login error. state = %v, code is nil", state)
  60.                         return
  61.                 }
  62.                 loginId := qrCode.LoginId
  63.                 token, err := enforcer.LoginById(loginId)
  64.                 if err != nil {
  65.                         fmt.Fprintf(w, "Login error: %s\n", err)
  66.                 }
  67.                 fmt.Fprintf(w, "%v login success. state = %v, token = %v", loginId, state, token)
  68.                 return
  69.         } else if state == model.CancelAuth {
  70.                 fmt.Fprintf(w, "QRCodeId be cancelled: %v", QRCodeId)
  71.                 return
  72.         }
  73.         fmt.Fprintf(w, "state = %v", state)
  74. }
  75. func cancelAuth(w http.ResponseWriter, req *http.Request) {
  76.         tempToken := req.URL.Query().Get("tempToken")
  77.         err := enforcer.CancelAuth(tempToken)
  78.         if err != nil {
  79.                 fmt.Fprintf(w, "CancelAuth() failed: %v", err)
  80.                 return
  81.         }
  82.         fmt.Fprint(w, "ConfirmAuth() success")
  83. }
  84. func confirmAuth(w http.ResponseWriter, req *http.Request) {
  85.         tempToken := req.URL.Query().Get("tempToken")
  86.         err := enforcer.ConfirmAuth(tempToken)
  87.         if err != nil {
  88.                 fmt.Fprintf(w, "ConfirmAuth() failed: %v", err)
  89.                 return
  90.         }
  91.         fmt.Fprint(w, "ConfirmAuth() success")
  92. }
复制代码
从最开始的流程和测试方法中也可以知道
首先我们需要在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。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

自由的羽毛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表