介紹
JSON Web Token (JWT), 是爲了在網絡應用環境間傳遞聲明而執行的一種基於JSON的開放標準((RFC 7519).該 Token 被設計爲緊湊且安全的,特別適用於分佈式站點的單點登錄(SSO)場景。JWT 的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該 Token 也可直接被用於認證,也可被加密。
使用
安裝
go get github.com/appleboy/gin-jwt
引入
import "github.com/appleboy/gin-jwt"
我目前使用的版本是 v2.5.0
.
<!-- more -->
創建中間件
設計 API 對象
type API struct {
App *apps.App // 業務對象
Router *gin.Engine // 路由
JWT *jwt.GinJWTMiddleware // jwt 對象
}
中間件對象:
api.JWT = &jwt.GinJWTMiddleware{
Realm: "gin jwt",
Key: []byte("secret key"),
Timeout: time.Hour,
MaxRefresh: time.Hour,
PayloadFunc: func(data interface{}) jwt.MapClaims {},
Authenticator: func(c *gin.Context) (interface{}, error) {},
Authorizator: func(data interface{}, c *gin.Context) bool {},
Unauthorized: func(c *gin.Context, code int, message string) {},
TokenLookup: "header: Authorization, query: token, cookie: jwt",
// TokenLookup: "query:token",
// TokenLookup: "cookie:token",
TokenHeadName: "Bearer",
TimeFunc: time.Now,
}
-
Realm
JWT標識 -
Key
服務端密鑰 -
Timeout
token 過期時間 -
MaxRefresh
token 更新時間 -
PayloadFunc
添加額外業務相關的信息 -
Authenticator
在登錄接口中使用的驗證方法,並返回驗證成功後的用戶對象。 -
Authorizator
登錄後其他接口驗證傳入的 token 方法 -
Unauthorized
驗證失敗後設置錯誤信息 -
TokenLookup
設置 token 獲取位置,一般默認在頭部的Authorization
中,或者 query的 token 字段,cookie 中的 jwt 字段。 -
TokenHeadName
Header中 token 的頭部字段,默認常用名稱Bearer
。 -
TimeFunc
設置時間函數
註冊階段
在註冊時如果要直接返回 token,那麼可以調用 TokenGenerator
來生成 token。
token, expire, err := c.JWT.TokenGenerator(strconv.Itoa(user.ID), *user)
TokenGenerator
的具體實現
func (mw *GinJWTMiddleware) TokenGenerator(userID string, data interface{}) (string, time.Time, error) {
// 根據簽名算法創建 token 對象
token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm))
// 獲取 claims
claims := token.Claims.(jwt.MapClaims)
// 設置業務中需要的額外信息
if mw.PayloadFunc != nil {
for key, value := range mw.PayloadFunc(data) {
claims[key] = value
}
}
// 過期時間
expire := mw.TimeFunc().UTC().Add(mw.Timeout)
claims["id"] = userID
claims["exp"] = expire.Unix()
claims["orig_iat"] = mw.TimeFunc().Unix()
// 生成 token
tokenString, err := mw.signedString(token)
if err != nil {
return "", time.Time{}, err
}
return tokenString, expire, nil
}
登錄階段
登錄時會調用 Authenticator
註冊的方法。
func (api *API) LoginAuthenticator(ctx *gin.Context) (interface{}, error) {
var params model.UserParams
if err := ctx.Bind(¶ms); err != nil {
return "", jwt.ErrMissingLoginValues
}
// 根據用戶名獲取用戶
user, err := api.App.GetUserByName(params.Username)
if err != nil {
return nil, err
}
// 驗證密碼
if user.AuthPassword(params.Password) {
return *user, nil
}
return nil, jwt.ErrFailedAuthentication
}
驗證 Token
其他接口在設置了中間件 Router.Use(api.JWT.MiddlewareFunc())
後,通過調用 Authorizator
方法來驗證。
func (api *API) LoginedAuthorizator(data interface{}, c *gin.Context) bool {
if id, ok := data.(string); ok {
return api.App.IsExistUser(id)
}
return false
}
在業務 Hander 中可以通過方法 jwt.ExtractClaims(ctx)
來獲取 payload 的信息。
深入
gin-jwt
依賴的 jwt
庫叫做 jwt-go
。下面來介紹一下這個庫。
核心的 Token
結構:
// A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
這個Token
結構體是用來生成 jwt
的 token。其中 Method
是用來表示簽名使用的算法。Header
是頭部jwt
的信息,還有 Claims
記錄額外的信息。
然後是生成簽名的方法,key 是服務端的密鑰。
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
// 將 Header 和 Claims 轉換成字符串然後 base64 之後拼接在一起。
if sstr, err = t.SigningString(); err != nil {
return "", err
}
// 使用簽名算法加密
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
解密 token 的對象叫做 Parser
type Parser struct {}
// 主要解析方法
func (p *Parser) ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
Parser 除了驗證 Token 外,還包括解碼 Header 和 Claims 的內容。