欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于gin的golang web開發(fā)之認(rèn)證利器jwt

 更新時(shí)間:2020年12月02日 14:11:26   作者:陳宏博  
這篇文章主要介紹了基于gin的golang web開發(fā)之認(rèn)證利器jwt,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

JSON Web Token(JWT)是一種很流行的跨域認(rèn)證解決方案,JWT基于JSON可以在進(jìn)行驗(yàn)證的同時(shí)附帶身份信息,對(duì)于前后端分離項(xiàng)目很有幫助。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWT由三部分組成,每個(gè)部分之間用點(diǎn).隔開,分別稱為HEADER、PAYLOAD和VERIFY SIGNATURE。HEADER和PAYLOAD經(jīng)過base64解碼后為JSON明文。

  1. HEADER包含兩個(gè)字段,alg指明JWT的簽名算法,typ固定為JWT
  2. PAYLOAD中包含JWT的聲明信息,標(biāo)準(zhǔn)中定義了iss、sub、aud等聲明字段,如果標(biāo)準(zhǔn)聲明不夠用的話,我們還可以增加自定義聲明。要注意兩點(diǎn),第一PAYLOAD只是經(jīng)過base64編碼,幾乎就等于是明文,不要包含敏感信息。第二不要在PAYLOAD中放入過多的信息,因?yàn)轵?yàn)證通過以后每一個(gè)請(qǐng)求都要包含JWT,信息太多的話會(huì)造成一些沒有必要的資源浪費(fèi)。
  3. VERIFY SIGNATURE為使用HEADER中指定的算法生成的簽名。例如alg:HS256簽名算法

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),密鑰)

了解完JWT的基本原理之后,我們來看一下在gin中是怎么使用JWT的。

引入gin-jwt中間件

在Gin中使用jwt有個(gè)開源項(xiàng)目gin-jwt,這項(xiàng)目幾乎包含了我們要用到的一切。例如定義PAYLOAD中的聲明、授權(quán)驗(yàn)證的方法、是否使用COOKIE等等。下面來看一下官網(wǎng)給出的例子。

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)
	}
}

我們可以看到j(luò)wt.GinJWTMiddleware用于聲明一個(gè)中間件。PayloadFunc方法中給默認(rèn)的PAYLOAD增加了id字段,取值為UserName。Authenticator認(rèn)證器,我們可以在這里驗(yàn)證用戶身份,參數(shù)為*gin.Context,所以在這里我們可以像寫Gin Handler那樣獲取到Http請(qǐng)求中的各種內(nèi)容。Authorizator授權(quán)器可以判斷判斷當(dāng)前JWT是否有權(quán)限繼續(xù)訪問。當(dāng)然還可以設(shè)置像過期時(shí)間,密鑰,是否設(shè)置COOKIE等其他選項(xiàng)。

登錄Handler

以上例子中配置了路由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整體邏輯還是比較簡(jiǎn)單的,檢查并調(diào)用前面設(shè)置的Authenticator方法,驗(yàn)證成功的話生成一個(gè)新的JWT,調(diào)用PayloadFunc方法設(shè)置PAYLOAD的自定義字段,根據(jù)SendCookie判斷是否需要在HTTP中設(shè)置COOKIE,最后調(diào)用LoginResponse方法設(shè)置返回值。

使用中間件

jwt-gin包提供了一個(gè)標(biāo)準(zhǔn)的Gin中間件,我們可以在需要驗(yàn)證JWT的路由上設(shè)置中間件。前面例子中對(duì)路由組/auth增加了JWT驗(yàn)證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方法在當(dāng)前上下文中獲取JWT,失敗的話返回未授權(quán)。接著會(huì)判斷JWT是否過期,最后前面設(shè)置的Authorizator方法驗(yàn)證是否有權(quán)限繼續(xù)訪問。

到此這篇關(guān)于基于gin的golang web開發(fā)之認(rèn)證利器jwt的文章就介紹到這了,更多相關(guān)gin的golang web開發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 在Go中編寫測(cè)試代碼的方法總結(jié)

    在Go中編寫測(cè)試代碼的方法總結(jié)

    在程序開發(fā)過程中,測(cè)試是非常重要的一環(huán),甚至有一種開發(fā)模式叫?TDD,先編寫測(cè)試,再編寫功能代碼,通過測(cè)試來推動(dòng)整個(gè)開發(fā)的進(jìn)行,可見測(cè)試在開發(fā)中的重要程度,為此,Go提供了testing框架來方便我們編寫測(cè)試,本文將向大家介紹在Go中如何編寫測(cè)試代碼
    2023-07-07
  • Go中http超時(shí)問題的排查及解決方法

    Go中http超時(shí)問題的排查及解決方法

    這篇文章主要介紹了Go中http超時(shí)問題的排查及解決方法,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Go語(yǔ)言并發(fā)編程 sync.Once

    Go語(yǔ)言并發(fā)編程 sync.Once

    這篇文章要介紹的是Go語(yǔ)言并發(fā)編程 sync.Once,sync.Once用于保證某個(gè)動(dòng)作只被執(zhí)行一次,可用于單例模式中,下面文章我們來介紹一下它的使用方法,需要的朋友可以參考一下
    2021-10-10
  • Go strconv包實(shí)現(xiàn)字符串和基本數(shù)據(jù)類型轉(zhuǎn)換的實(shí)例詳解

    Go strconv包實(shí)現(xiàn)字符串和基本數(shù)據(jù)類型轉(zhuǎn)換的實(shí)例詳解

    在Go語(yǔ)言(Golang)的編程實(shí)踐中,strconv包是一個(gè)非常重要的標(biāo)準(zhǔn)庫(kù),它提供了在基本數(shù)據(jù)類型(如整型、浮點(diǎn)型、布爾型)和字符串之間的轉(zhuǎn)換功能,本文給大家介紹了關(guān)于Go語(yǔ)言字符串轉(zhuǎn)換strconv,需要的朋友可以參考下
    2024-09-09
  • 在golang中使用cel的用法詳解

    在golang中使用cel的用法詳解

    CEL?是一種非圖靈完備的表達(dá)式語(yǔ)言?,旨在快速、可移植且執(zhí)行安全,CEL?可以單獨(dú)使用,也可以嵌入到其他的產(chǎn)品中,本文將給大家介紹一下golang中如何使用cel,需要的朋友可以參考下
    2023-11-11
  • GoLand一鍵上傳項(xiàng)目到遠(yuǎn)程服務(wù)器的方法步驟

    GoLand一鍵上傳項(xiàng)目到遠(yuǎn)程服務(wù)器的方法步驟

    我們開發(fā)項(xiàng)目常常將項(xiàng)目上傳到linux遠(yuǎn)程服務(wù)器上來運(yùn)行,本文主要介紹了GoLand一鍵上傳項(xiàng)目到遠(yuǎn)程服務(wù)器的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 如何讓shell終端和goland控制臺(tái)輸出彩色的文字

    如何讓shell終端和goland控制臺(tái)輸出彩色的文字

    這篇文章主要介紹了如何讓shell終端和goland控制臺(tái)輸出彩色的文字的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-05-05
  • Golang實(shí)現(xiàn)文件傳輸功能

    Golang實(shí)現(xiàn)文件傳輸功能

    這篇文章主要為大家詳細(xì)介紹了Golang實(shí)現(xiàn)文件傳輸功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • Golang指針隱式間接引用詳解

    Golang指針隱式間接引用詳解

    在 Go中,指針隱式解引用是指通過指針直接訪問指針?biāo)赶虻闹?,而不需要顯式地使用 * 運(yùn)算符來解引用指針,這篇文章主要介紹了Golang指針隱式間接引用,需要的朋友可以參考下
    2023-05-05
  • 用GO實(shí)現(xiàn)IP門禁優(yōu)化網(wǎng)絡(luò)流量管理

    用GO實(shí)現(xiàn)IP門禁優(yōu)化網(wǎng)絡(luò)流量管理

    這篇文章主要為大家介紹了用GO實(shí)現(xiàn)IP門禁優(yōu)化網(wǎng)絡(luò)流量管理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12

最新評(píng)論