小白啓程1: Go語言 beego + JWT 實現註冊登錄驗證

1.1 安裝Beego

go get -u github.com/beego/beego/v2
go get -u github.com/beego/bee/v2

beego 是一個快速開發 Go 應用的 HTTP 框架,他可以用來快速開發 API、Web 及後端服務等各種應用,是一個 RESTful 的框架,主要設計靈感來源於 tornado、sinatra 和 flask 這三個框架,但是結合了 Go 本身的一些特性(interface、struct 嵌入等)而設計的一個框架。

官方文檔 https://beego.me/docs/intro/

1.2 安裝JWT

go get github.com/dgrijalva/jwt-go

JSON Web Token (JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作爲JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因爲它是數字簽名的。

Github Repo: https://github.com/dgrijalva/jwt-go

2. Beego + JWT 註冊登錄實現

main.go
連接數據庫 (這裏我用的是mysql)+ 運行主程序


package main

import (
	"firstAPI/models"
	_ "firstAPI/routers"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
)

func init() {
   
   
	orm.Debug = true

	if err1 := orm.RegisterDriver("mysql", orm.DRMySQL); err1 != nil {
   
   
		logs.Error(err1.Error())
	}

	orm.RegisterModel(new(models.User))

	if err2 := orm.RegisterDataBase("default","mysql","root:12345678@tcp(127.0.0.1:3306)/testdb");err2 != nil {
   
   
		logs.Error(err2.Error())
		panic(err2.Error())
	}
	fmt.Println("Connected to the database")
	orm.RunSyncdb("default", false, true)
}

func main() {
   
   
	if beego.BConfig.RunMode == "dev" {
   
   
		beego.BConfig.WebConfig.DirectoryIndex = true
		beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
	}

	beego.Run()

models/utils.go
產生token,驗證token,更新token

package models

import(
	"crypto/rand"
	"errors"
	"fmt"
	"github.com/astaxie/beego/logs"
	"github.com/dgrijalva/jwt-go"
	"golang.org/x/crypto/scrypt"
	"io"
	"log"
	"time"
)

// JWT : HEADER PAYLOAD SIGNATURE
const (
	SecretKEY              string = "JWT-Secret-Key"
	DEFAULT_EXPIRE_SECONDS int    = 180 // default expired 1 minute
	PasswordHashBytes             = 16
)

// This struct is the payload
type MyCustomClaims struct {
   
   
	UserID int `json:"UserID"`
	jwt.StandardClaims
}

// This struct is the parsing of token payload
type JwtPayload struct {
   
   
	Username  string `json:"Username"`
	UserID    int    `json:"UserID"`
	IssuedAt  int64  `json:"Iat"`
	ExpiresAt int64  `json:"Exp"`
}

//generate token
func GenerateToken(loginInfo *LoginRequest, userID int, expiredSeconds int) (tokenString string, err error) {
   
   
	if expiredSeconds == 0 {
   
   
		expiredSeconds = DEFAULT_EXPIRE_SECONDS
	}

	// Create the Claims
	mySigningKey := []byte(SecretKEY)
	expireAt := time.Now().Add(time.Second * time.Duration(expiredSeconds)).Unix()
	logs.Info("Token will be expired at ", time.Unix(expireAt, 0))

	user := *loginInfo
	claims := MyCustomClaims{
   
   
		userID,
		jwt.StandardClaims{
   
   
			Issuer:    user.Username,
			IssuedAt:  time.Now().Unix(),
			ExpiresAt: expireAt,
		},
	}

	// Create the token using your claims
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	// Signs the token with a secret
	tokenStr, err := token.SignedString(mySigningKey)
	if err != nil {
   
   
		return "", errors.New("error: failed to generate token")
	}

	return tokenStr, nil
}

//validate token
func ValidateToken(tokenString string) (*JwtPayload, error) {
   
   
	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{
   
   },
		func(token *jwt.Token) (interface{
   
   }, error) {
   
   
			return []byte(SecretKEY), nil
		})

	claims, ok := token.Claims.(*MyCustomClaims)
	if ok && token.Valid {
   
   
		log.Println("ok && token valid")
		logs.Info("%v %v", claims.UserID, claims.StandardClaims.ExpiresAt)
		logs.Info("Token was issued at ", time.Now().Unix())
		logs.Info("Token will be expired at ", time.Unix(claims.StandardClaims.ExpiresAt, 0))

		return &JwtPayload{
   
   
			Username:  claims.StandardClaims.Issuer,
			UserID:    claims.UserID,
			IssuedAt:  claims.StandardClaims.IssuedAt,
			ExpiresAt: claims.StandardClaims.ExpiresAt,
		}, nil
	} else {
   
   
		fmt.Println(err)
		return nil, errors.New("error: failed to validate token")
	}
}

//update token
func RefreshToken(tokenString string) (newTokenString string, err error) {
   
   
	// get previous token
	token, err := jwt.ParseWithClaims(tokenString, &MyCustomClaims{
   
   },
		func(token *jwt.Token) (interface{
   
   }, error) {
   
   
			return []byte(SecretKEY), nil
		})

	claims, ok := token.Claims.(*MyCustomClaims)
	if !ok || !token.Valid {
   
   
		return "", err
	}

	mySigningKey := []byte(SecretKEY)
	expireAt := time.Now().Add(time.Second * time.Duration(DEFAULT_EXPIRE_SECONDS)).Unix() //new expired
	newClaims := MyCustomClaims{
   
   
		claims.UserID,
		jwt.StandardClaims{
   
   
			Issuer:    claims.StandardClaims.Issuer, //name of token issue
			IssuedAt:  time.Now().Unix(),            //time of token issue
			ExpiresAt: expireAt,// new expired
		},
	}

	// generate new token with new claims
	newToken := jwt.NewWithClaims(jwt.SigningMethodHS256, newClaims)
	// sign the token with a secret
	tokenStr, err := newToken.SignedString(mySigningKey)
	if err != nil {
   
   
		return "", errors.New("error: failed to generate new fresh json web token")
	}

	return tokenStr, nil
}


Hash密碼

// generate salt
func GenerateSalt() (salt string, err error) {
   
   
	buf := make([]byte, PasswordHashBytes)
	if _, err := io.ReadFull(rand.Reader, buf); err != nil {
   
   
		return "", errors.New("error: failed to generate user's salt")
	}

	return fmt.Sprintf("%x", buf), nil
}

// generate password hash
func GeneratePassHash(password string, salt string) (hash string, err error) {
   
   
	h, err := scrypt.Key([]byte(password), []byte(salt), 16384, 8, 1, PasswordHashBytes)
	if err != nil {
   
   
		return "", errors.New("error: failed to generate password hash")
	}

	return fmt.Sprintf("%x", h), nil
}

驗證前端傳回來的token是否過期

func CheckStatus(tokenString string) (string,int64) {
   
   
	jp, err := ValidateToken(tokenString)

	if err != nil {
   
   
		// if token has already expired
		fmt.Println("Your token has expired, Please log in again! ")
		return "", -1
	}

	timeDiff := jp.ExpiresAt - time.Now().Unix()
	fmt.Println("timeDiff = ", timeDiff)
	if timeDiff <= 30 {
   
   
		// if token is close to expiration, refresh the token
		fmt.Println("Your token would soon be expired")
		newToken, err := RefreshToken(tokenString)
		if err == nil {
   
   
			return newToken, timeDiff
		}
	}
	// if token is valid, do nothing
	fmt.Println("Your token is good ")
	return tokenString,timeDiff
}

models/user.go
登錄和註冊 (request 根據自己需求定義)

func DoLogin(lr *LoginRequest) (*LoginResponse, int, error){
   
   
	// get username and password
	username := lr.Username
	password := lr.Password

	// validate user name and password is they are empty
	if len(username) == 0 || len(password) == 0 {
   
   
		return nil, http.StatusBadRequest,errors.New("error: username or password is empty")
	}

	o := orm.NewOrm()

	// check if the username exists
	user := &User{
   
   Username: username}
	err := o.Read(user,"Username")
	if err != nil {
   
   
		return nil, http.StatusBadRequest, errors.New("error: username doesn't exist")
	}

	// generate the password hash
	hash, err := GeneratePassHash(password,user.Salt)
	if err != nil {
   
   
		return nil, http.StatusBadRequest, err
	}
	if hash != user.Password {
   
   
		return nil, http.StatusBadRequest,errors.New("error: password is error")
	}

	// generate token
	tokenString, err := GenerateToken(lr, user.Id, 0)
	if err != nil {
   
   
		return nil, http.StatusBadRequest, err
	}

	return &LoginResponse{
   
   
		Username: user.Username,
		UserID: user.Id,
		Token: tokenString,
	},http.StatusOK,nil
}

func DoCreateUser(cr *CreateRequest)(*CreateResponse,int,error){
   
   
	o := orm.NewOrm()

	// check if username exists
	userNameCheck := User{
   
   Username: cr.Username}
	err := o.Read(&userNameCheck,"Username")
	if err == nil {
   
   
		return nil, http.StatusBadRequest, errors.New("username has already existed")
	}

	//generate salt
	saltKey, err := GenerateSalt()
	if err != nil {
   
   
		logs.Info(err.Error())
		return nil, http.StatusBadRequest, err
	}

	// generate password hash
	hash, err := GeneratePassHash(cr.Password,saltKey)
	if err != nil {
   
   
		logs.Info(err.Error())
		return nil, http.StatusBadRequest,err
	}

	// create user
	user := User{
   
   }
	user.Username = cr.Username
	user.Password = hash
	user.Salt = saltKey

	_, err = o.Insert(&user)
	if err != nil {
   
   
		logs.Info(err.Error())
		return nil, http.StatusBadRequest,err
	}

	return &CreateResponse{
   
   
		UserID:user.Id,
		Username: user.Username,
	}, http.StatusOK,nil
}

3. Golang JWT實例

  • 分享一個個人感覺非常好的Golang JWT實例 點我

感謝閱讀🙏~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章