Gin(十二):配合JWT

文章首發於 我的博客 ISLAND 和 我的微信公衆號 代碼獵奇站前端

在先後端分離的項目中,愈來愈多的項目採用 JWT 代替傳統的 cookie ,這裏咱們來使用 JWT 結合 Gin 來做爲一個登陸受權和權限校驗。mysql

🔑什麼是 JWT

JWT 的全稱叫作 JSON WEB TOKEN,在目前先後端系統中使用較多。git

JWT 構成

JWT 是由三段構成的。分別是 HEADER,PAYLOAD,VERIFY SIGNATURE,它們生成的信息經過 . 分割。github

HEADER

header 是由 一個 typalg 組成,typ 會指明爲 JWT,而 alg 是所使用的加密算法。算法

{
  "alg": "HS256",
  "typ": "JWT"
}
複製代碼

PAYLOAD

payload 是 JWT 的載體,也就是咱們要承載的信息。這段信息是咱們能夠自定義的,能夠定義咱們要存放什麼信息,那些字段。該部分信息不宜過多,它會影響 JWT 生成的大小,還有就是請勿將敏感數據存入該部分,該端數據前端是能夠解析獲取 token 內信息的。sql

官方給了七個默認字段,咱們能夠不所有使用,也能夠加入咱們須要的字段。json

名稱 含義
Audience 表示JWT的受衆
ExpiresAt 失效時間
Id 簽發編號
IssuedAt 簽發時間
Issuer 簽發人
NotBefore 生效時間
Subject 主題

VERIFY SIGNATURE

這也是 JWT 的最後一段,該部分是由算法計算完成的。後端

對剛剛的 header 進行 base64Url 編碼,對 payload 進行 base64Url 編碼,兩端完成編碼後經過 . 進行鏈接起來。安全

base64UrlEncode(header).base64UrlEncode(payload)
複製代碼

完成上述步驟後,就要經過咱們 header 裏指定的加密算法對上部分進行加密,同時咱們還要插入咱們的一個密鑰,來確保個人 JWT 簽發是安全的。服務器

這即是咱們的第三部分。

當三部分都完成後,經過使用 . 將三部分分割,生成了上圖所示的 JWT 。

JWT 登陸原理

簡單的說就是當用戶登陸的時候,服務器校驗登陸名稱和密碼是否正確,正確的話,會生成 JWT 返回給客戶端。客戶端獲取到 JWT 後要進行保存,以後的每次請求都會講 JWT 攜帶在頭部,每次服務器都會獲取頭部的 JWT 是否正確,若是正確則正確執行該請求,否者驗證失敗,從新登陸。

🔒Gin 生成 JWT

go 語言的 JWT 庫有不少。jwt.io 上也給出了不少 。這裏使用 jwt-go

"github.com/dgrijalva/jwt-go"

咱們對登陸方法進行改造。

// 省略代碼
expiresTime := time.Now().Unix() + int64(config.OneDayOfHours)
claims := jwt.StandardClaims{
    Audience:  user.Username,     // 受衆
    ExpiresAt: expiresTime,       // 失效時間
    Id:        string(user.ID),   // 編號
    IssuedAt:  time.Now().Unix(), // 簽發時間
    Issuer:    "gin hello",       // 簽發人
    NotBefore: time.Now().Unix(), // 生效時間
    Subject:   "login",           // 主題
}
var jwtSecret = []byte(config.Secret)
tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 省略代碼
複製代碼

這裏的 config.OneDayOfHours 設定了過時時間,這裏設定了一天。經過 StandardClaims 生成標準的載體,也就是上文提到的七個字段,其中 編號設定爲 用戶 id。其中的 jwtSecret 是咱們設定的密鑰,

咱們這裏經過 HS256 算法生成 tokenClaims ,這就是咱們的 HEADER 部分和 PAYLOAD。

token, err := tokenClaims.SignedString(jwtSecret)
複製代碼

這樣便生成了咱們的 token 。咱們要將咱們的 token 和 Bearer 拼接在一塊兒,同時中間用空格隔開。

token =  "Bearer "+ token
複製代碼

生成 Bearer Token 。

當咱們用戶進行登陸的時候,就能夠經過該片斷生成 JWT。

下面是完整代碼:

func CreateJwt(ctx *gin.Context) {
	// 獲取用戶
	user := &model.User{}
	result := &model.Result{
		Code:    200,
		Message: "登陸成功",
		Data:    nil,
	}
	if e := ctx.BindJSON(&user); e != nil {
		result.Message = "數據綁定失敗"
		result.Code = http.StatusUnauthorized
		ctx.JSON(http.StatusUnauthorized, gin.H{
			"result": result,
		})
	}
	u := user.QueryByUsername()
	if u.Password == user.Password {
		expiresTime := time.Now().Unix() + int64(config.OneDayOfHours)
		claims := jwt.StandardClaims{
			Audience:  user.Username,     // 受衆
			ExpiresAt: expiresTime,       // 失效時間
			Id:        string(user.ID),   // 編號
			IssuedAt:  time.Now().Unix(), // 簽發時間
			Issuer:    "gin hello",       // 簽發人
			NotBefore: time.Now().Unix(), // 生效時間
			Subject:   "login",           // 主題
		}
		var jwtSecret = []byte(config.Secret)
		tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
		if token, err := tokenClaims.SignedString(jwtSecret); err == nil {
			result.Message = "登陸成功"
			result.Data = "Bearer " + token
			result.Code = http.StatusOK
			ctx.JSON(result.Code, gin.H{
				"result": result,
			})
		} else {
			result.Message = "登陸失敗"
			result.Code = http.StatusOK
			ctx.JSON(result.Code, gin.H{
				"result": result,
			})
		}
	} else {
		result.Message = "登陸失敗"
		result.Code = http.StatusOK
		ctx.JSON(result.Code, gin.H{
			"result": result,
		})
	}
}
複製代碼

經過 .http 請求測試,結果以下

{
  "result": {
    "code": 200,
    "message": "登陸成功",
    "data": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTY0MzksImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NjQxOSwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk2NDE5LCJzdWIiOiJsb2dpbiJ9.CpacmfBSMgmK2TgrT-KwNB60bsvwgyryGQ0pWZr8laU"
  }
}
複製代碼

這個便完成了token的生成。

🔐Gin 校驗 Token

那麼,接下來就須要完成 token 的驗證。

還記得以前咱們驗證用戶是否受權採用的辦法嗎?是的,在中間件裏查看用戶 cookie。一樣的方法,咱們這裏校驗用戶 JWT 是否有效。

編寫咱們的中間件。

新創建 middleware/Auth.go

首先先編寫咱們的解析 token 方法,parseToken()

func parseToken(token string) (*jwt.StandardClaims, error) {
	jwtToken, err := jwt.ParseWithClaims(token, &jwt.StandardClaims{}, func(token *jwt.Token) (i interface{}, e error) {
		return []byte(config.Secret), nil
	})
	if err == nil && jwtToken != nil {
		if claim, ok := jwtToken.Claims.(*jwt.StandardClaims); ok && jwtToken.Valid {
			return claim, nil
		}
	}
	return nil, err
}
複製代碼

經過傳入咱們的 token , 來對 token 進行解析。

完整的中間件代碼

func Auth() gin.HandlerFunc {
	return func(context *gin.Context) {
		result := model.Result{
			Code:    http.StatusUnauthorized,
			Message: "沒法認證,從新登陸",
			Data:    nil,
		}
		auth := context.Request.Header.Get("Authorization")
		if len(auth) == 0 {
			context.Abort()
			context.JSON(http.StatusUnauthorized, gin.H{
				"result": result,
			})
		}
		auth = strings.Fields(auth)[1]
		// 校驗token
		_, err := parseToken(auth)
		if err != nil {
			context.Abort()
			result.Message = "token 過時" + err.Error()
			context.JSON(http.StatusUnauthorized, gin.H{
				"result": result,
			})
		} else {
			println("token 正確")
		}
		context.Next()
	}
}
複製代碼

首先在請求頭獲取 token ,而後對先把 token 進行解析,將 Bearer 和 JWT 拆分出來,將 JWT 進行校驗。

咱們只須要對咱們須要校驗的路由進行添加中間件校驗便可。

router.GET("/", middleware.Auth(), func(context *gin.Context) {
		context.JSON(http.StatusOK, time.Now().Unix())
	})
複製代碼

當咱們訪問 / 的時候就須要攜帶 token 了

GET http://localhost:8080
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiIxMjMiLCJleHAiOjE1NjQ3OTQzNjIsImp0aSI6Ilx1MDAwMCIsImlhdCI6MTU2NDc5NDM0MiwiaXNzIjoiZ2luIGhlbGxvIiwibmJmIjoxNTY0Nzk0MzQyLCJzdWIiOiJsb2dpbiJ9.uQxGMsftyVFtYIGwQVm1QB2djw-uMfDbw81E5LMjliU
複製代碼

✍總結

本章節對什麼是 JWT,Gin 中如何使用 JWT 作了介紹。可是不要過於迷信 JWT,JWT 還有不少問題,好比說 JWT 失效只能是時間過時,若是修改密碼或者帳戶註銷等操做須要咱們另外添加邏輯判斷。適合的地方選用適合的技術才能發揮最大的優點。

👨‍💻本章節代碼

Github

歷史文章

Gin(一):Hello
Gin(二):路由Router
Gin(三):模板tmpl
Gin(四):表單提交校驗和模型綁定
Gin(五):鏈接MySQL
Gin(六):文件的上傳
Gin(七):中間件的使用和定義
Gin(八):Cookie的使用
Gin(九):生成restful接口
Gin(十):集成 Swagger
Gin(十一)集成ORM-gorm
Gin(十二)集成JWT

相關文章
相關標籤/搜索