eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT由三部分组成,每一个部分之间用点.
隔开,分别称为HEADER、PAYLOAD和VERIFY SIGNATURE。HEADER和PAYLOAD通过base64解码后为JSON明文。git
alg
指明JWT的签名算法,typ
固定为JWT
。iss
、sub
、aud
等声明字段,若是标准声明不够用的话,咱们还能够增长自定义声明。要注意两点,第一PAYLOAD只是通过base64编码,几乎就等因而明文,不要包含敏感信息。第二不要在PAYLOAD中放入过多的信息,由于验证经过之后每个请求都要包含JWT,信息太多的话会形成一些没有必要的资源浪费。alg:HS256
签名算法HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),密钥)
了解完JWT的基本原理以后,咱们来看一下在gin中是怎么使用JWT的。github
在Gin中使用jwt有个开源项目gin-jwt
,这项目几乎包含了咱们要用到的一切。例如定义PAYLOAD中的声明、受权验证的方法、是否使用COOKIE等等。下面来看一下官网给出的例子。golang
package main import ( "log" "net/http" "os" "time" jwt "github.com/appleboy/gin-jwt/v2" "github.com/gin-gonic/gin" ) type login struct { Username string `form:"username" json:"username" binding:"required"` Password string `form:"password" json:"password" binding:"required"` } var identityKey = "id" func helloHandler(c *gin.Context) { claims := jwt.ExtractClaims(c) user, _ := c.Get(identityKey) c.JSON(200, gin.H{ "userID": claims[identityKey], "userName": user.(*User).UserName, "text": "Hello World.", }) } type User struct { UserName string FirstName string LastName string } func main() { port := os.Getenv("PORT") r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) if port == "" { port = "8000" } authMiddleware, err := jwt.New(&jwt.GinJWTMiddleware{ Realm: "test zone", Key: []byte("secret key"), Timeout: time.Hour, MaxRefresh: time.Hour, IdentityKey: identityKey, PayloadFunc: func(data interface{}) jwt.MapClaims { if v, ok := data.(*User); ok { return jwt.MapClaims{ identityKey: v.UserName, } } return jwt.MapClaims{} }, IdentityHandler: func(c *gin.Context) interface{} { claims := jwt.ExtractClaims(c) return &User{ UserName: claims[identityKey].(string), } }, Authenticator: func(c *gin.Context) (interface{}, error) { var loginVals login if err := c.ShouldBind(&loginVals); err != nil { return "", jwt.ErrMissingLoginValues } userID := loginVals.Username password := loginVals.Password if (userID == "admin" && password == "admin") || (userID == "test" && password == "test") { return &User{ UserName: userID, LastName: "Bo-Yi", FirstName: "Wu", }, nil } return nil, jwt.ErrFailedAuthentication }, Authorizator: func(data interface{}, c *gin.Context) bool { if v, ok := data.(*User); ok && v.UserName == "admin" { return true } return false }, Unauthorized: func(c *gin.Context, code int, message string) { c.JSON(code, gin.H{ "code": code, "message": message, }) }, TokenLookup: "header: Authorization, query: token, cookie: jwt", TokenHeadName: "Bearer", TimeFunc: time.Now, }) if err != nil { log.Fatal("JWT Error:" + err.Error()) } errInit := authMiddleware.MiddlewareInit() if errInit != nil { log.Fatal("authMiddleware.MiddlewareInit() Error:" + errInit.Error()) } r.POST("/login", authMiddleware.LoginHandler) r.NoRoute(authMiddleware.MiddlewareFunc(), func(c *gin.Context) { claims := jwt.ExtractClaims(c) log.Printf("NoRoute claims: %#v\n", claims) c.JSON(404, gin.H{"code": "PAGE_NOT_FOUND", "message": "Page not found"}) }) auth := r.Group("/auth") auth.GET("/refresh_token", authMiddleware.RefreshHandler) auth.Use(authMiddleware.MiddlewareFunc()) { auth.GET("/hello", helloHandler) } if err := http.ListenAndServe(":"+port, r); err != nil { log.Fatal(err) } }
咱们能够看到jwt.GinJWTMiddleware用于声明一个中间件。PayloadFunc方法中给默认的PAYLOAD增长了id字段,取值为UserName。Authenticator认证器,咱们能够在这里验证用户身份,参数为*gin.Context,因此在这里咱们能够像写Gin Handler那样获取到Http请求中的各类内容。Authorizator受权器能够判断判断当前JWT是否有权限继续访问。固然还能够设置像过时时间,密钥,是否设置COOKIE等其余选项。web
以上例子中配置了路由r.POST("/login", authMiddleware.LoginHandler)
下面咱们来看一下登陆过程是怎样的。算法
func (mw *GinJWTMiddleware) LoginHandler(c *gin.Context) { if mw.Authenticator == nil { mw.unauthorized(c, http.StatusInternalServerError, mw.HTTPStatusMessageFunc(ErrMissingAuthenticatorFunc, c)) return } data, err := mw.Authenticator(c) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c)) return } // Create the token token := jwt.New(jwt.GetSigningMethod(mw.SigningAlgorithm)) claims := token.Claims.(jwt.MapClaims) if mw.PayloadFunc != nil { for key, value := range mw.PayloadFunc(data) { claims[key] = value } } expire := mw.TimeFunc().Add(mw.Timeout) claims["exp"] = expire.Unix() claims["orig_iat"] = mw.TimeFunc().Unix() tokenString, err := mw.signedString(token) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrFailedTokenCreation, c)) return } // set cookie if mw.SendCookie { expireCookie := mw.TimeFunc().Add(mw.CookieMaxAge) maxage := int(expireCookie.Unix() - mw.TimeFunc().Unix()) if mw.CookieSameSite != 0 { c.SetSameSite(mw.CookieSameSite) } c.SetCookie( mw.CookieName, tokenString, maxage, "/", mw.CookieDomain, mw.SecureCookie, mw.CookieHTTPOnly, ) } mw.LoginResponse(c, http.StatusOK, tokenString, expire) }
LoginHandler总体逻辑仍是比较简单的,检查并调用前面设置的Authenticator方法,验证成功的话生成一个新的JWT,调用PayloadFunc方法设置PAYLOAD的自定义字段,根据SendCookie判断是否须要在HTTP中设置COOKIE,最后调用LoginResponse方法设置返回值。json
jwt-gin
包提供了一个标准的Gin中间件,咱们能够在须要验证JWT的路由上设置中间件。前面例子中对路由组/auth
增长了JWT验证auth.Use(authMiddleware.MiddlewareFunc())
。后端
func (mw *GinJWTMiddleware) MiddlewareFunc() gin.HandlerFunc { return func(c *gin.Context) { mw.middlewareImpl(c) } } func (mw *GinJWTMiddleware) middlewareImpl(c *gin.Context) { claims, err := mw.GetClaimsFromJWT(c) if err != nil { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(err, c)) return } if claims["exp"] == nil { mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrMissingExpField, c)) return } if _, ok := claims["exp"].(float64); !ok { mw.unauthorized(c, http.StatusBadRequest, mw.HTTPStatusMessageFunc(ErrWrongFormatOfExp, c)) return } if int64(claims["exp"].(float64)) < mw.TimeFunc().Unix() { mw.unauthorized(c, http.StatusUnauthorized, mw.HTTPStatusMessageFunc(ErrExpiredToken, c)) return } c.Set("JWT_PAYLOAD", claims) identity := mw.IdentityHandler(c) if identity != nil { c.Set(mw.IdentityKey, identity) } if !mw.Authorizator(identity, c) { mw.unauthorized(c, http.StatusForbidden, mw.HTTPStatusMessageFunc(ErrForbidden, c)) return } c.Next() }
GetClaimsFromJWT方法在当前上下文中获取JWT,失败的话返回未受权。接着会判断JWT是否过时,最后前面设置的Authorizator方法验证是否有权限继续访问。跨域
文章出处:基于gin的golang web开发:认证利器jwtcookie