基於gin的golang web開發:實現使用者登入

陳巨集博發表於2020-12-11

前文分別介紹過了Restygin-jwt兩個包,Resty是一個HTTP和REST客戶端,gin-jwt是一個實現了JWT的Gin中介軟體。本文將使用這兩個包來實現一個簡單的使用者登入功能。

環境準備

實現登入功能之前要提前準備一個用於查詢使用者是否存在的服務。訪問服務http://127.0.0.1:18081/users?username=root時返回使用者root的相關資訊

{
    "total": 1,
    "data": [
        {
            "id": 1,
            "username": "root",
            "password": "CGUx1FN++xS+4wNDFeN6DA==",
            "nickname": "超級管理員",
            "mobile": "13323232323"
        }
    ]
}

返回結果中password欄位AES加密後的結果。當引數username傳入其他字串時返回null

{
    "total": 0,
    "data": null
}

好了準備工作到此結束,下面來看一下如何實現登入功能。

實現認證

首先實現呼叫查詢使用者服務的方法。

func FindUser(userName string) (user sysUser.SysUser, err error) {
	client := resty.New().SetRetryCount(3)
	resp, err := client.R().
		SetQueryParams(map[string]string{
			"username": userName,
		}).
		SetResult(&PagedUser{}).
		Get("http://127.0.0.1:18081/users")
	if err != nil {
		log.Panicln("FindUser err: ", err.Error())
	}
	response := resp.Result().(*PagedUser)
	if response.Total == 1 {
		user = response.Data[0]
		return
	}
	err = errors.New("使用者不存在")
	return
}

這裡我們建立了一個Resty客戶端,並設定了3次重試,依照服務的要求傳入username引數,然後通過Total值判斷使用者是否存在,使用者存在的話返回使用者資訊,否則返回錯誤。

接下來我們實現有關jwt驗證的部分。

var identityKey = "id"

type login struct {
	Username string `form:"username" json:"username" binding:"required"`
	Password string `form:"password" json:"password" binding:"required"`
}

type User struct {
	Id       int
	UserName string
	NickName string
}

func JwtMiddleware() (authMiddleware *jwt.GinJWTMiddleware, err error) {
	authMiddleware, err = jwt.New(&jwt.GinJWTMiddleware{
		Realm:       "test zone",
		Key:         []byte("secret key"),
		Timeout:     time.Hour,
		MaxRefresh:  time.Hour,
		IdentityKey: identityKey,
		PayloadFunc: func(data interface{}) jwt.MapClaims {
			if v, ok := data.(*User); ok {
				return jwt.MapClaims{
					identityKey: v.UserName,
				}
			}
			return jwt.MapClaims{}
		},
		IdentityHandler: func(c *gin.Context) interface{} {
			claims := jwt.ExtractClaims(c)
			return &User{
				UserName: claims[identityKey].(string),
			}
		},
		Authenticator: func(c *gin.Context) (interface{}, error) {
			var loginVals login
			if err := c.ShouldBind(&loginVals); err != nil {
				return "", jwt.ErrMissingLoginValues
			}
			userID := loginVals.Username
			password := loginVals.Password

			user, err := http_service.FindUser(userID)
			if err != nil {
				return nil, jwt.ErrFailedAuthentication
			}

			encrypt := utils.PasswordEncrypt(password, userID)
			if encrypt != user.Password.String {
				return nil, jwt.ErrFailedAuthentication
			}

			return &User{
				Id:       user.Id,
				UserName: user.Username.String,
				NickName: user.Nickname.String,
			}, nil
		},
		Authorizator: func(data interface{}, c *gin.Context) bool {
			if v, ok := data.(*User); ok && v.UserName == "admin" {
				return true
			}

			return false
		},
		Unauthorized: func(c *gin.Context, code int, message string) {
			c.JSON(code, gin.H{
				"code":    code,
				"message": message,
			})
		},
		TokenLookup: "header: Authorization, query: token, cookie: jwt_middleware",
		TokenHeadName: "Bearer",
		TimeFunc: time.Now,
	})
	return
}

以上程式碼在基於gin的golang web開發:認證利器jwt一文中有詳細的解釋,我們重點來看一下使用者驗證的部分:Authenticator

方法ShouldBind對引數進行模型繫結,不熟悉模型繫結的話可以檢視前文基於gin的golang web開發:模型繫結。然後呼叫FindUser方法檢查使用者是否存在,如果使用者存在的話還需要驗證一下使用者密碼是否正確。全部驗證通過返回User結構體,進入gin-jwt的後續流程。

最後一步在Gin中增加使用者登入路由。

func main() {
	r := gin.Default()

	authMiddleware, err := JwtMiddleware()
	if err != nil {
		log.Fatal("JWT Error:" + err.Error())
	}

	errInit := authMiddleware.MiddlewareInit()

	if errInit != nil {
		log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error())
	}

	r.POST("/login", authMiddleware.LoginHandler)

	r.Run(":8090")
}

大功告成,現在呼叫/login介面,並提供正確的使用者名稱和密碼,不出意外的話會得到一個成功的JSON,裡面包含了驗證通過的JWT。

文章出處:基於gin的golang web開發:實現使用者登入

相關文章