Gin 集成: 集成 JWT

  1. 介紹

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

jwt-go 是使用Go語言實現的Json web token (JWT), 目前GitHub Start 9.8k, 源碼地址: https://github.com/dgrijalva/jwt-go,從版本 3.2.1 開始, 源碼地址變更爲: github.com/golang-jwt/jwt, 需要下載最新版本時, 可以使用這個地址。

1.2 集成示意圖

  1. 配置

2.1 編輯主配置

文件位置:./config.yaml

app:
  ...
log:
  ...
mysql:
  ...
jwt:
  secret: liuqh.icu # jwt生成密鑰
  issuer: 猿碼記 # 簽發人
  expire: 3h # 有效時間,值如: 30s|10min|1h

2.2 新增結構體

文件位置:  ./config/jwt.go

/**
 * @Description JWT配置
 **/
package config

import "time"
// JSON WEB TOKEN 配置
type jwt struct {
 Secret string        `yaml:"secret"`
 Issuer string        `yaml:"issuer"`
 Expire time.Duration `yaml:"expire"`
}
  1. 編輯中間件

文件位置:./middleware/jwt.go,功能包括有中間件函數/創建Token/解析Token

3.1 中間件函數

/**
 * @Description JWT中間件
 **/
package middleware

import (
 "52lu/go-import-template/global"
 "52lu/go-import-template/model/dao"
 "52lu/go-import-template/model/request"
 "52lu/go-import-template/model/response"
 "errors"
 "github.com/gin-gonic/gin"
 "github.com/golang-jwt/jwt"
 "go.uber.org/zap"
 "net/http"
 "time"
)
/**
 * @description: JWT中間件
 * @return func(ctx *gin.Context)
 */
func JWTAuthMiddleware() func(ctx *gin.Context) {
 return func(ctx *gin.Context) {
  // 獲取參數中的token
  token := getToken(ctx)
  global.GvaLogger.Sugar().Infof("token: %s",token)
  if token == "" {
   response.Error(ctx,"Token不能爲空!")
   // 中斷請求
   ctx.Abort()
   return
  }
  // 驗證Token
  userClaim, err := ParseToken(token)
  if err != nil {
   response.ErrorWithToken(ctx,"Token error :" + err.Error())
   // 中斷請求
   ctx.Abort()
   return
  }
  // 設置到上下文中
  setContextData(ctx,userClaim,token)
  // 繼續請求後續流程
  ctx.Next()
 }
}
// 設置數據到上下文
func setContextData(ctx *gin.Context,userClaim *request.UserClaims,token string)  {
 userDao := &dao.UserDao{
  Uid: userClaim.Uid,
 }
 user, err := userDao.FindUser()
 if err != nil {
  response.Error(ctx,"用戶不存在!")
  // 中斷請求
  ctx.Abort()
  return
 }
 user.Token = token
 ctx.Set("userClaim",userClaim)
 ctx.Set("user",user)
}

// 從請求中獲取Token
func getToken(ctx *gin.Context) string {
 var token string
 // 從header中獲取
 token = ctx.Request.Header.Get("TOKEN")
 if token != "" {
  return token
 }
 // 獲取當前請求方法
 if ctx.Request.Method == http.MethodGet {
  // 從Get請求中獲取Token
  token, ok := ctx.GetQuery("token")
  if ok {
   return token
  }
 }
 // 從POST中和獲取
 if ctx.Request.Method == http.MethodPost {
  postParam := make(map[string]interface{})
  _ = ctx.ShouldBindJSON(&postParam)
  token, ok := postParam["token"]
  if ok {
   return token.(string)
  }
 }
 return ""
}

3.2 創建 Token

// 創建Jwt
func CreateToken(uid uint) (string, error) {
 newWithClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, &request.UserClaims{
  StandardClaims: &jwt.StandardClaims{
   ExpiresAt: time.Now().Add(global.GvaConfig.Jwt.Expire).Unix(), // 有效期
   Issuer:    global.GvaConfig.Jwt.Issuer,                  // 簽發人
   IssuedAt:  time.Now().Unix(),                            // 簽發時間
  },
  Uid: uid,
 })
 return newWithClaims.SignedString([]byte(global.GvaConfig.Jwt.Secret))
}

3.3 解析 Token

// 驗證JWT
func ParseToken(tokenString string) (*request.UserClaims, error) {
 var err error
 var token *jwt.Token
 token, err = jwt.ParseWithClaims(tokenString, &request.UserClaims{}, func(token *jwt.Token) (interface{}, error) {
  return []byte(global.GvaConfig.Jwt.Secret), nil
 })
 if err != nil {
  global.GvaLogger.Error("解析JWT失敗", zap.String("error", err.Error()))
  return nil, err
 }
 // 斷言
 userClaims, ok := token.Claims.(*request.UserClaims)
 // 驗證
 if !ok || !token.Valid {
  return nil, errors.New("JWT驗證失敗")
 }
 return userClaims, nil
}
  1. 註冊路由

註冊路由流程

4.1 不需要登錄路由

1. 註冊路由

文件位置:router/user_router.go

/**
 * @description: 用戶相關的路由
 * @param engine
 */
func InitUserRouter(engine *gin.Engine) {
 // 不需要登錄的路由
 noLoginGroup := engine.Group("v1/user")
 {
  // 登錄
  noLoginGroup.POST("login", v1.Login)
 }
}

2. 路由綁定函數

文件位置:./api/v1/user_api.go

/**
 * @description: TODO 用戶賬號密碼登錄
 * @param ctx
 */
func Login(ctx *gin.Context) {
 // 綁定參數
 var loginParam request.LoginParam
 _ = ctx.ShouldBindJSON(&loginParam)
  //...(省略)
 // 生成token
 token, err := middleware.CreateToken(userRecord.ID)
 if err != nil {
  global.GvaLogger.Sugar().Errorf("登錄失敗,Token生成異常:%s", err)
  response.Error(ctx, "登錄失敗,賬號或者密碼錯誤!")
  return
 }
 userRecord.Token = token
 response.OkWithData(ctx, userRecord)
}

3. 請求返回

4.2 需要登錄路由

1. 註冊路由

文件位置:router/user_router.go

/**
 * @description: 用戶相關的路由
 * @param engine
 */
func InitUserRouter(engine *gin.Engine) {
 // 不需要登錄的路由
 ...
 // 需要登錄
 tokenGroup := engine.Group("v1/user").Use(middleware.JWTAuthMiddleware())
 {
  tokenGroup.POST("/detail", v1.GetUser)
 }
}

2. 路由綁定函數

文件位置:./api/v1/user_api.go

// 查詢用戶信息
func GetUser(ctx *gin.Context) {
 // 從上下文中獲取用戶信息,(經過中間件邏輯時,已經設置到上下文)
 user, _ := ctx.Get("user")
 response.OkWithData(ctx, user)
 return
}

3. 請求返回

關注【猿碼記】微信公衆號,回覆【gin 集成】獲取源碼。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0ayMVsTdbI3OmgKOyyWCKQ