本文將使用這個庫來實現(xiàn)我們生成JWT和解析JWT的功能撇簿。
使用
go get github.com/golang-jwt/jwt/v4
默認Claim
如果我們直接使用JWT中默認的字段,沒有其他定制化的需求則可以直接使用這個包中的和方法快速生成和解析token。
// 用于簽名的字符串
var mySigningKey = []byte("liwenzhou.com")
// GenRegisteredClaims 使用默認聲明創(chuàng)建jwt
func GenRegisteredClaims() (string, error) {
// 創(chuàng)建 Claims
claims := &jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24)), // 過期時間
Issuer: "qimi", // 簽發(fā)人
}
// 生成token對象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 生成簽名字符串
return token.SignedString(mySigningKey)
}
// ParseRegisteredClaims 解析jwt
func ValidateRegisteredClaims(tokenString string) bool {
// 解析token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return mySigningKey, nil
})
if err != nil { // 解析token失敗
return false
}
return token.Valid
}
自定義Claims
我們需要定制自己的需求來決定JWT中保存哪些數(shù)據(jù),比如我們規(guī)定在JWT中要存儲username信息袄秩,那么我們就定義一個MyClaims結構體如下:
// CustomClaims 自定義聲明類型 并內(nèi)嵌jwt.RegisteredClaims
// jwt包自帶的jwt.RegisteredClaims只包含了官方字段
// 假設我們這里需要額外記錄一個username字段,所以要自定義結構體
// 如果想要保存更多信息轻绞,都可以添加到這個結構體中
type CustomClaims struct {
// 可根據(jù)需要自行添加字段
Username string `json:"username"`
jwt.RegisteredClaims // 內(nèi)嵌標準的聲明
}
然后我們定義JWT的過期時間搏嗡,這里以24小時為例:
constTokenExpireDuration=time.Hour*24
接下來還需要定義一個用于簽名的字符串:
// CustomSecret 用于加鹽的字符串
varCustomSecret=[]byte("夏天夏天悄悄過去")
生成JWT
我們可以根據(jù)自己的業(yè)務需要封裝一個生成 token 的函數(shù)窿春。
// GenToken 生成JWT
func GenToken(username string) (string, error) {
// 創(chuàng)建一個我們自己的聲明
claims := CustomClaims{
username, // 自定義字段
jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(TokenExpireDuration)),
Issuer: "my-project", // 簽發(fā)人
},
}
// 使用指定的簽名方法創(chuàng)建簽名對象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用指定的secret簽名并獲得完整的編碼后的字符串token
return token.SignedString(CustomSecret)
}
解析JWT
根據(jù)給定的 JWT 字符串,解析出數(shù)據(jù)。
// ParseToken 解析JWT
func ParseToken(tokenString string) (*CustomClaims, error) {
// 解析token
// 如果是自定義Claim結構體則需要使用 ParseWithClaims 方法
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (i interface{}, err error) {
// 直接使用標準的Claim則可以直接使用Parse方法
//token, err := jwt.Parse(tokenString, func(token *jwt.Token) (i interface{}, err error) {
return CustomSecret, nil
})
if err != nil {
return nil, err
}
// 對token對象中的Claim進行類型斷言
if claims, ok := token.Claims.(*CustomClaims); 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) {
// 用戶發(fā)送用戶名和密碼過來
var user UserInfo
err := c.ShouldBind(&user)
if err != nil {
c.JSON(http.StatusOK, gin.H{
"code": 2001,
"msg": "無效的參數(shù)",
})
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之后,后續(xù)就會攜帶著Token再來請求我們的其他接口纽甘,這個時候就需要對這些請求的Token進行校驗操作了良蛮,很顯然我們應該實現(xiàn)一個檢驗Token的中間件,具體實現(xiàn)如下:
// JWTAuthMiddleware 基于JWT的認證中間件
func JWTAuthMiddleware() func(c *gin.Context) {
return func(c *gin.Context) {
// 客戶端攜帶Token有三種方式 1.放在請求頭 2.放在請求體 3.放在URI
// 這里假設Token放在Header的Authorization中悍赢,并使用Bearer開頭
// 這里的具體實現(xiàn)方式要依據(jù)你的實際業(yè)務情況決定
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的函數(shù)來解析它
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() // 后續(xù)的處理函數(shù)可以用過c.Get("username")來獲取當前請求的用戶信息
}
}
注冊一個/home路由,發(fā)個請求驗證一下吧左权。
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},
})
}