Use go to encapsulate and implement code scanning login
I will talk about the implementation and usage of token-go scan code login.
Github: https://github.com/weloe/token-go
Scan 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
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 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
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
package model
type QRCodeState int
// QRCode State
const (
WaitScan QRCodeState = 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.
package model
type QRCodeState int
// QRCode State
const (
WaitScan QRCodeState = 1
WaitAuth QRCodeState = 2
ConfirmAuth QRCodeState = 3
CancelAuth QRCodeState = 4
Expired QRCodeState = 5
)
Initialize QR code status
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
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
Use Adapter to get
func (e *Enforcer) GetQRCode(QRCodeId string) *model.QRCode {
return e.getQRCode(QRCodeId)
}
Get QR code status
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
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
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
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
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\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")
}
You can also know from the initial process and testing methods
/qrcode/create
with the parameter QR code id . The backend calls the method of generating the QR code (which needs to be implemented by yourself). 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 checks the token sent from the APP (use theenforcer.isLoginByToken()
method of the framework to judge) whether it is in the login state, use enforcer. GetLoginId()
obtains the corresponding loginId, and then calls 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, the temporary token is sent to the backend /qrcode/confirmAuth
or /qrcode/cancelAuth
, and the backend calls the enforcer.ConfirmAuth()
or enforcer.CancelAuth()
method to agree or cancel. Authorization.
After initializing the QR code status, the Web side must continue to request the backend/qrcode/getState
. The backend calls the GetQRCodeState
method to obtain the QR code status. If the QR code status is timed out, that is, Expired, the front end will delete the QR code information. It prompts that the QR code has expired. Regenerate the QR code. If the obtained status is equal to the confirmation authorization ConfirmAuth, perform the login operation enforcer.LoginById()
and return the login credential token.
Top comments (0)