文章首發於 我的博客 ISLAND 和 我的微信公衆號 代碼獵奇站前端
在先後端分離的項目中,愈來愈多的項目採用 JWT
代替傳統的 cookie
,這裏咱們來使用 JWT
結合 Gin
來做爲一個登陸受權和權限校驗。mysql
JWT 的全稱叫作 JSON WEB TOKEN,在目前先後端系統中使用較多。git
JWT 是由三段構成的。分別是 HEADER,PAYLOAD,VERIFY SIGNATURE,它們生成的信息經過 .
分割。github
header 是由 一個 typ
和 alg
組成,typ
會指明爲 JWT,而 alg
是所使用的加密算法。算法
{
"alg": "HS256",
"typ": "JWT"
}
複製代碼
payload 是 JWT 的載體,也就是咱們要承載的信息。這段信息是咱們能夠自定義的,能夠定義咱們要存放什麼信息,那些字段。該部分信息不宜過多,它會影響 JWT 生成的大小,還有就是請勿將敏感數據存入該部分,該端數據前端是能夠解析獲取 token 內信息的。sql
官方給了七個默認字段,咱們能夠不所有使用,也能夠加入咱們須要的字段。json
名稱 | 含義 |
---|---|
Audience | 表示JWT的受衆 |
ExpiresAt | 失效時間 |
Id | 簽發編號 |
IssuedAt | 簽發時間 |
Issuer | 簽發人 |
NotBefore | 生效時間 |
Subject | 主題 |
這也是 JWT 的最後一段,該部分是由算法計算完成的。後端
對剛剛的 header 進行 base64Url 編碼,對 payload 進行 base64Url 編碼,兩端完成編碼後經過 .
進行鏈接起來。安全
base64UrlEncode(header).base64UrlEncode(payload)
複製代碼
完成上述步驟後,就要經過咱們 header 裏指定的加密算法對上部分進行加密,同時咱們還要插入咱們的一個密鑰,來確保個人 JWT 簽發是安全的。服務器
這即是咱們的第三部分。
當三部分都完成後,經過使用 .
將三部分分割,生成了上圖所示的 JWT 。
簡單的說就是當用戶登陸的時候,服務器校驗登陸名稱和密碼是否正確,正確的話,會生成 JWT 返回給客戶端。客戶端獲取到 JWT 後要進行保存,以後的每次請求都會講 JWT 攜帶在頭部,每次服務器都會獲取頭部的 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的生成。
那麼,接下來就須要完成 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 失效只能是時間過時,若是修改密碼或者帳戶註銷等操做須要咱們另外添加邏輯判斷。適合的地方選用適合的技術才能發揮最大的優點。
Gin(一):Hello
Gin(二):路由Router
Gin(三):模板tmpl
Gin(四):表單提交校驗和模型綁定
Gin(五):鏈接MySQL
Gin(六):文件的上傳
Gin(七):中間件的使用和定義
Gin(八):Cookie的使用
Gin(九):生成restful接口
Gin(十):集成 Swagger
Gin(十一)集成ORM-gorm
Gin(十二)集成JWT