在前後端分離的項目中,越來越多的項目採用 JWT 代替傳統的 cookie ,這裡我們來使用 JWT 結合 Gin 來作為一個登錄授權和權限校驗。
什麼是 JWT
JWT 的全稱叫做 JSON WEB TOKEN,在目前前後端系統中使用較多。
JWT 構成
JWT 是由三段構成的。分別是 HEADER,PAYLOAD,VERIFY SIGNATURE,它們生成的信息通過 . 分割。
在前後端分離的項目中,越來越多的項目採用 JWT 代替傳統的 cookie ,這裡我們來使用 JWT 結合 Gin 來作為一個登錄授權和權限校驗。
什麼是 JWT
JWT 的全稱叫做 JSON WEB TOKEN,在目前前後端系統中使用較多。
JWT 構成
JWT 是由三段構成的。分別是 HEADER,PAYLOAD,VERIFY SIGNATURE,它們生成的信息通過 . 分割。
HEADER
header 是由 一個 typ 和 alg 組成,typ 會指明為 JWT,而 alg 是所使用的加密算法。
{
"alg": "HS256",
"typ": "JWT"
}
PAYLOAD
payload 是 JWT 的載體,也就是我們要承載的信息。這段信息是我們可以自定義的,可以定義我們要存放什麼信息,那些字段。該部分信息不宜過多,它會影響 JWT 生成的大小,還有就是請勿將敏感數據存入該部分,該端數據前端是可以解析獲取 token 內信息的。
官方給了七個默認字段,我們可以不全部使用,也可以加入我們需要的字段。
- 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 是否正確,如果正確則正確執行該請求,否者驗證失敗,重新登錄。
在前後端分離的項目中,越來越多的項目採用 JWT 代替傳統的 cookie ,這裡我們來使用 JWT 結合 Gin 來作為一個登錄授權和權限校驗。
什麼是 JWT
JWT 的全稱叫做 JSON WEB TOKEN,在目前前後端系統中使用較多。
JWT 構成
JWT 是由三段構成的。分別是 HEADER,PAYLOAD,VERIFY SIGNATURE,它們生成的信息通過 . 分割。
HEADER
header 是由 一個 typ 和 alg 組成,typ 會指明為 JWT,而 alg 是所使用的加密算法。
{
"alg": "HS256",
"typ": "JWT"
}
PAYLOAD
payload 是 JWT 的載體,也就是我們要承載的信息。這段信息是我們可以自定義的,可以定義我們要存放什麼信息,那些字段。該部分信息不宜過多,它會影響 JWT 生成的大小,還有就是請勿將敏感數據存入該部分,該端數據前端是可以解析獲取 token 內信息的。
官方給了七個默認字段,我們可以不全部使用,也可以加入我們需要的字段。
- 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 ,學習 Gin 從這裡開始
Gin(三):與模板配合使用 tmpl,go web 開發最火框架之一
Gin(四):接收表單數據和模型綁定,Go語言最火web框架之一
Gin(八):cookies使用,Go語言web最火框架之一
Gin(九):生成restful 接口,go語言最火web框架之一