Use go to encapsulate and implement scan code login
This article is about using go to design and develop your own lightweight login library/framework – Qiu Bo – Blog Park (cnblogs.com) scan code login business article, will talk about the implementation of scan code login, and add to the library/framework New functions, finally instructions on how to use them
Github: https://github.com/weloe/token-go
Scan QR code login process
First we need to know the QR code login process
- Open the login page, display a QR code, and poll the QR code status (web)
- After opening the APP and scanning the QR code, the APP displays confirmation and cancel buttons (app)
- The login page displays the scanned user avatar and other information (web)
- The user clicks on the APP to confirm login (app)
- The login page learns from the polling QR code status that the user has confirmed login and obtains the login credentials (web)
- The page login is successful and enters the main application page (web)
We can know that the logged in QR code has the following states:
- Waiting for scanning
- Scanned code, waiting for user confirmation
- The QR code has been scanned and the user agrees to authorize
- The QR code has been scanned and the user has canceled the authorization.
- expired
And our code-scanning client (usually a mobile app) can modify the status of the QR code.
- Confirm that the code has been scanned
- Agree to authorize
- Cancel authorization
Implementation ideas
What we encapsulate is mainly the state maintenance of QR codes, not including the generation of QR codes. The generation of QR codes is left to the user.
The commonly used methods for QR code status are as follows.
// QRCode api //Initialize QR code status CreateQRCodeState(QRCodeId string, timeout int64) error // Get the remaining time of the QR code GetQRCodeTimeout(QRCodeId string) int64 // Get QR code information GetQRCode(QRCodeId string) *model.QRCode // Get QR code status GetQRCodeState(QRCodeId string) model.QRCodeState // Confirm that the code has been scanned Scanned(QRCodeId string, loginId string) (string, error) // Agree to authorize ConfirmAuth(QRCodeTempToken string) error // Cancel authorization CancelAuth(QRCodeTempToken string) error
QRCodeId is used by us as the unique identifier of the QR code status.
When creating a QR code, we need to pass in the QRCodeId and timeout to set the timeout of the QR code. After all, the QR code cannot be used permanently.
Of course, the prerequisite for confirming that the code has been scanned is that you are logged in, so we use loginId as a parameter to bind it to QRCodeId.
For authorization and cancellation of authorization, we use the temporary Token returned by the api to confirm the scan code to perform operations.
The storage and acquisition of information are obtained using the Adapter inside the framework.
Code implementation
QR code status and information
First we need to set the QR code status
Waiting to scan code–1
Scanned code, waiting for user confirmation–2
The QR code has been scanned and the user agrees to authorize–3
The QR code has been scanned and the user has canceled authorization–4
Expired – 5
package model type QRCodeState int // QRCode State const ( WaitScanQRCodeState = 1 WaitAuth QRCodeState = 2 ConfirmAuth QRCodeState = 3 CancelAuth QRCodeState = 4 Expired QRCodeState = 5 )
The information needed to maintain the QR code, that is, the unique id of the QR code, the current status of the QR code, and the unique id of the user for whom the QR code belongs.
type QRCode struct {<!-- --> ID string StateQRCodeState LoginId string } func NewQRCode(id string) *QRCode {<!-- --> return & amp;QRCode{<!-- -->id: id, State: WaitScan} }
Initialize QR code status
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L229
Before scanning the QR code in the APP, we must first create a QR code status and set it to WaitScan, which is 1. To create QR code information, we use the Adapter interface inside our framework to store it.
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 is a splicing method for stored keys.
Get the remaining time of the QR code
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L319
Use our Adapter to get it through QRCodeId
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)) }
Get QR code information
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L301
Use Adapter to get
func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode {<!-- --> return e.getQRCode(QRCodeId) }
Get QR code status
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L311
Also use Adapter to obtain
//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 }
Delete QR code information
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)) }
Confirm scan code
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L234
To confirm the code scanning, you must first determine whether the QR code exists, and then verify whether the status of the QR code is WaitScan, which is 1. After verification, bind the user’s unique loginId, and finally create a temporary token with a value of QRCodeId and return it. This temporary token is used for authorization and cancellation of authorization.
// 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 }
Agree to authorize
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L257
To agree to authorization, we need to use the temporary token returned when we confirm the scan code. First, we need to verify the temporary token. The ParseTempToken method is an interface for verifying the temporary token and obtaining the value corresponding to the token. After verifying the token, obtain the QRCodeId, and then verify the status corresponding to the QRCodeId. It should be WaitAuth waiting for authorization, which is 2. The last step is to change the QR code status to ConfirmAuth which is 3. Of course, don’t forget to delete the temporary 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 }
Cancel authorization
https://github.com/weloe/token-go/blob/b85a297b4eae1ee730059be277d7aa83658c1fe4/enforcer_manager_api.go#L280
To cancel authorization, we also need to use the temporary token returned when we confirm the scan code. First, we need to verify the temporary token. The ParseTempToken method is the method to verify the temporary token. Through this method, we can obtain the QRCodeId value corresponding to the token. After verifying the token, obtain the QRCodeId, and then verify the status corresponding to the QRCodeId. It should be WaitAuth waiting for authorization, which is 2. The last step is to change the QR code status to CancelAuth which is 4. Also don’t forget to delete the temporary 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 }
Test
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) } }
How to use
https://github.com/weloe/token-go/blob/master/examples/qrcode/qrcode-server.go
Install 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\\ ", 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 canceled: %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") }
You can also know from the initial process and testing methods
First, we need to generate a QR code on the Web side (the client that needs to scan the code to log in) and then request the backend /qrcode/create
with the parameter QR code id, and the backend calls the QR code generated method (you need to implement it yourself), and then call the enforcer.CreateQRCodeState()
method to initialize the QR code state.
Scan the QR code from the APP and request the backend /qrcode/scanned
. The backend first verifies the token judgment sent from the APP (use the framework’s enforcer.isLoginByToken()
code> method to determine whether) is in the login state, use enforcer.GetLoginId()
to obtain the corresponding loginId, and then call the enforcer.Scanned()
method. Then return the temporary token.
After receiving the temporary token, the APP chooses to agree or cancel the authorization, that is, transfer the temporary token to the backend /qrcode/confirmAuth
or /qrcode/cancelAuth
, and the backend calls enforcer.CancelAuth()
method agrees or cancels authorization.
After initializing the QR code state, the Web side must continue to request the backend /qrcode/getState
, and the backend calls the GetQRCodeState
method to obtain the QR code status. If the QR code If the status is timed out, that is, Expired, the front end will delete the QR code information, prompt that the QR code has expired, and regenerate the QR code. If the status obtained is equal to the confirmation authorization ConfirmAuth, the login operation will be performed enforcer.LoginById()
, returns the login credentials token.