基于gin的golang web開發(fā)之認(rèn)證利器jwt
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)過(guò)base64解碼后為JSON明文。
- HEADER包含兩個(gè)字段,
alg指明JWT的簽名算法,typ固定為JWT。 - PAYLOAD中包含JWT的聲明信息,標(biāo)準(zhǔn)中定義了
iss、sub、aud等聲明字段,如果標(biāo)準(zhǔn)聲明不夠用的話,我們還可以增加自定義聲明。要注意兩點(diǎn),第一PAYLOAD只是經(jīng)過(guò)base64編碼,幾乎就等于是明文,不要包含敏感信息。第二不要在PAYLOAD中放入過(guò)多的信息,因?yàn)轵?yàn)證通過(guò)以后每一個(gè)請(qǐng)求都要包含JWT,信息太多的話會(huì)造成一些沒(méi)有必要的資源浪費(fèi)。 - VERIFY SIGNATURE為使用HEADER中指定的算法生成的簽名。例如
alg:HS256簽名算法
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),密鑰)
了解完JWT的基本原理之后,我們來(lái)看一下在gin中是怎么使用JWT的。
引入gin-jwt中間件
在Gin中使用jwt有個(gè)開源項(xiàng)目gin-jwt,這項(xiàng)目幾乎包含了我們要用到的一切。例如定義PAYLOAD中的聲明、授權(quán)驗(yàn)證的方法、是否使用COOKIE等等。下面來(lái)看一下官網(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ù)訪問(wèn)。當(dāng)然還可以設(shè)置像過(guò)期時(shí)間,密鑰,是否設(shè)置COOKIE等其他選項(xiàng)。
登錄Handler
以上例子中配置了路由r.POST("/login", authMiddleware.LoginHandler)下面我們來(lái)看一下登錄過(guò)程是怎樣的。
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是否過(guò)期,最后前面設(shè)置的Authorizator方法驗(yàn)證是否有權(quán)限繼續(xù)訪問(wèn)。
到此這篇關(guān)于基于gin的golang web開發(fā)之認(rèn)證利器jwt的文章就介紹到這了,更多相關(guān)gin的golang web開發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 一文詳解如何在Golang中實(shí)現(xiàn)JWT認(rèn)證與授權(quán)
- Go語(yǔ)言中Gin框架使用JWT實(shí)現(xiàn)登錄認(rèn)證的方案
- go-micro微服務(wù)JWT跨域認(rèn)證問(wèn)題
- go語(yǔ)言使用jwt認(rèn)證的實(shí)現(xiàn)
- go語(yǔ)言beego框架jwt身份認(rèn)證實(shí)現(xiàn)示例
- 詳解Django配置JWT認(rèn)證方式
- 詳解Go-JWT-RESTful身份認(rèn)證教程
- Go實(shí)現(xiàn)JWT認(rèn)證中間件的項(xiàng)目實(shí)戰(zhàn)
相關(guān)文章
GoLang中socket心跳檢測(cè)的實(shí)現(xiàn)
本文主要介紹了GoLang中socket心跳檢測(cè)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-02-02
使用Lumberjack+zap進(jìn)行日志切割歸檔操作
這篇文章主要介紹了使用Lumberjack+zap進(jìn)行日志切割歸檔操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
golang http 連接超時(shí)和傳輸超時(shí)的例子
今天小編就為大家分享一篇golang http 連接超時(shí)和傳輸超時(shí)的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-07-07
Go語(yǔ)言開發(fā)kube-scheduler整體架構(gòu)深度剖析
這篇文章主要為大家介紹了Go語(yǔ)言開發(fā)kube-scheduler整體架構(gòu)深度剖析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04
golang?gin框架實(shí)現(xiàn)大文件的流式上傳功能
這篇文章主要介紹了golang?gin框架中實(shí)現(xiàn)大文件的流式上傳,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07
Go 語(yǔ)言結(jié)構(gòu)實(shí)例分析
在本篇文章里小編給大家整理的是一篇關(guān)于Go 語(yǔ)言結(jié)構(gòu)實(shí)例分析的相關(guān)知識(shí)點(diǎn),有興趣的朋友們可以學(xué)習(xí)下。2021-07-07
Go語(yǔ)言{}大括號(hào)的特殊用法實(shí)例探究
這篇文章主要為大家介紹了Go語(yǔ)言{}大括號(hào)的特殊用法實(shí)例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
Go語(yǔ)言對(duì)JSON數(shù)據(jù)進(jìn)行序列化和反序列化
這篇文章介紹了Go語(yǔ)言對(duì)JSON數(shù)據(jù)進(jìn)行序列化和反序列化的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07

