首頁 > 軟體

gin框架中使用JWT的定義需求及解析

2022-04-16 16:00:17

什麼是JWT?

JWT全稱JSON Web Token是一種跨域認證解決方案,屬於一個開放的標準,它規定了一種Token實現方式,目前多用於前後端分離專案和OAuth3.0業務場景下。

為什麼需要JWT?

在之前的一些web專案中,我們通常使用的是Cookie-Session模式實現使用者認證。相關流程大致如下:

  • 使用者在瀏覽器端填寫使用者名稱和密碼,並行送給伺服器端
  • 伺服器端對使用者名稱和密碼校驗通過後會生成一份儲存當前使用者相關資訊的session資料和一個與之對應的標識(通常稱為session_id)
  • 伺服器端返回響應時將上一步的session_id寫入使用者瀏覽器的Cookie
  • 後續使用者來自該瀏覽器的每次請求都會自動攜帶包含session_id的Cookie
  • 伺服器端通過請求中的session_id就能找到之前儲存的該使用者那份session資料,從而獲取該使用者的相關資訊。

這種方案依賴於使用者端(瀏覽器)儲存Cookie,並且需要在伺服器端儲存使用者的session資料。

在行動網際網路時代,我們的使用者可能使用瀏覽器也可能使用APP來存取我們的服務,我們的web應用可能是前後端分開部署在不同的埠,有時候我們還需要支援第三方登入,這下Cookie-Session的模式就有些力不從心了。

JWT就是一種基於Token的輕量級認證模式,伺服器端認證通過後,會生成一個JSON物件,經過簽名後得到一個Token(令牌)再發回給使用者,使用者後續請求只需要帶上這個Token,伺服器端解密之後就能獲取該使用者的相關資訊了。

想要連線JWT的原理,推薦大家閱讀:JWT入門教學

生成JWT和解析JWT

我們在這裡直接使用jwt-go這個庫來實現我們生成JWT和解析JWT的功能。

定義需求

我們需要客製化自己的需求來決定JWT中儲存哪些資料,比如我們規定在JWT中要儲存username資訊,那麼我們就定義一個MyClaims結構體如下:

import (
	"github.com/dgrijalva/jwt-go"
)
// MyClaims 自定義宣告結構體並內嵌jwt.StandardClaims
// jwt包自帶的jwt.StandardClaims只包含了官方欄位
// 我們這裡需要額外記錄一個username欄位,所以要自定義結構體
// 如果想要儲存更多資訊,都可以新增到這個結構體中
type MyClaims struct {
	Username string `json:"username"`
	jwt.StandardClaims
}

然後我們定義JWT的過期時間,這裡以2小時為例:

const TokenExpireDuration = time.Hour * 2

接下來還需要定義Secret:

var MySecret = []byte("夏天夏天悄悄過去")

生成JWT

// GenToken 生成JWT
func GenToken(username string) (string, error) {
	// 建立一個我們自己的宣告
	c := MyClaims{
		"username", // 自定義欄位
		jwt.StandardClaims{
			ExpiresAt: time.Now().Add(TokenExpireDuration).Unix(), // 過期時間
			Issuer:    "my-project",                               // 簽發人
		},
	}
	// 使用指定的簽名方法建立簽名物件
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, c)
	// 使用指定的secret簽名並獲得完整的編碼後的字串token
	return token.SignedString(MySecret)
}

解析JWT

// ParseToken 解析JWT
func ParseToken(tokenString string) (*MyClaims, error) {
	// 解析token
	token, err := jwt.ParseWithClaims(tokenString, &MyClaims{}, func(token *jwt.Token) (i interface{}, err error) {
		return MySecret, nil
	})
	if err != nil {
		return nil, err
	}
	if claims, ok := token.Claims.(*MyClaims); ok && token.Valid { // 校驗token
		return claims, nil
	}
	return nil, errors.New("invalid token")
}

在gin框架中使用JWT

首先我們註冊一條路由/auth,對外提供獲取Token的渠道:

r.POST("/auth", authHandler)

我們的authHandler定義如下:

func authHandler(c *gin.Context) {
	// 使用者傳送使用者名稱和密碼過來
	var user UserInfo
	err := c.ShouldBind(&user)
	if err != nil {
		c.JSON(http.StatusOK, gin.H{
			"code": 2001,
			"msg":  "無效的引數",
		})
		return
	}
	// 校驗使用者名稱和密碼是否正確
	if user.Username == "q1mi" && user.Password == "q1mi123" {
		// 生成Token
		tokenString, _ := GenToken(user.Username)
		c.JSON(http.StatusOK, gin.H{
			"code": 2000,
			"msg":  "success",
			"data": gin.H{"token": tokenString},
		})
		return
	}
	c.JSON(http.StatusOK, gin.H{
		"code": 2002,
		"msg":  "鑑權失敗",
	})
	return
}

使用者通過上面的介面獲取Token之後,後續就會攜帶著Token再來請求我們的其他介面,這個時候就需要對這些請求的Token進行校驗操作了,很顯然我們應該實現一個檢驗Token的中介軟體,具體實現如下:

// JWTAuthMiddleware 基於JWT的認證中介軟體
func JWTAuthMiddleware() func(c *gin.Context) {
	return func(c *gin.Context) {
		// 使用者端攜帶Token有三種方式 1.放在請求頭 2.放在請求體 3.放在URI
		// 這裡假設Token放在Header的Authorization中,並使用Bearer開頭
		// 這裡的具體實現方式要依據你的實際業務情況決定
		authHeader := c.Request.Header.Get("Authorization")
		if authHeader == "" {
			c.JSON(http.StatusOK, gin.H{
				"code": 2003,
				"msg":  "請求頭中auth為空",
			})
			c.Abort()
			return
		}
		// 按空格分割
		parts := strings.SplitN(authHeader, " ", 2)
		if !(len(parts) == 2 && parts[0] == "Bearer") {
			c.JSON(http.StatusOK, gin.H{
				"code": 2004,
				"msg":  "請求頭中auth格式有誤",
			})
			c.Abort()
			return
		}
		// parts[1]是獲取到的tokenString,我們使用之前定義好的解析JWT的函數來解析它
		mc, err := ParseToken(parts[1])
		if err != nil {
			c.JSON(http.StatusOK, gin.H{
				"code": 2005,
				"msg":  "無效的Token",
			})
			c.Abort()
			return
		}
		// 將當前請求的username資訊儲存到請求的上下文c上
		c.Set("username", mc.Username)
		c.Next() // 後續的處理常式可以用過c.Get("username")來獲取當前請求的使用者資訊
	}
}

註冊一個/home路由,發個請求驗證一下吧。

r.GET("/home", JWTAuthMiddleware(), homeHandler)
func homeHandler(c *gin.Context) {
	username := c.MustGet("username").(string)
	c.JSON(http.StatusOK, gin.H{
		"code": 2000,
		"msg":  "success",
		"data": gin.H{"username": username},
	})
}

如果不想自己實現上述功能,你也可以使用Github上別人封裝好的包。

以上就是gin框架中使用JWT的定義需求及解析的詳細內容,更多關於gin框架中使用JWT的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com