Golang 在gin框架中如何使用JWT鑒權(quán)
什么是JWT
JWT,全稱 JSON Web Token,是一種開放標(biāo)準(zhǔn)(RFC 7519),用于安全地在雙方之間傳遞信息。尤其適用于身份驗證和授權(quán)場景。JWT 的設(shè)計允許信息在各方之間安全地、 compactly(緊湊地)傳輸,因為其自身包含了所有需要的認(rèn)證信息,從而減少了需要查詢數(shù)據(jù)庫或會話存儲的需求。
JWT主要由三部分組成,通過.
連接:
- Header(頭部):描述JWT的元數(shù)據(jù),通常包括類型(通常是
JWT
)和使用的簽名算法(如HS256
、RS256
等)。 - Payload(載荷):包含聲明(claims),即用戶的相關(guān)信息。這些信息可以是公開的,也可以是私有的,但應(yīng)避免放入敏感信息,因為該部分可以被解碼查看。載荷中的聲明可以驗證,但不加密。
- Signature(簽名):用于驗證JWT的完整性和來源。它是通過將Header和Payload分別進(jìn)行Base64編碼后,再與一個秘鑰(secret)一起通過指定的算法(如HMAC SHA256)計算得出的。
JWT的工作流程大致如下:
- 認(rèn)證階段:用戶向服務(wù)器提供憑證(如用戶名和密碼)。服務(wù)器驗證憑證無誤后,生成一個JWT,其中包含用戶標(biāo)識符和其他聲明,并使用秘鑰對其進(jìn)行簽名。
- 使用階段:客戶端收到JWT后,可以在后續(xù)的每個請求中將其放在HTTP請求頭中發(fā)送給服務(wù)器,以此證明自己的身份。
- 驗證階段:服務(wù)器收到JWT后,會使用相同的秘鑰驗證JWT的簽名,確保其未被篡改,并檢查過期時間等其他聲明,從而決定是否允許執(zhí)行請求。
JWT的優(yōu)勢在于它的無狀態(tài)性,服務(wù)器不需要存儲會話信息,這減輕了服務(wù)器的壓力,同時也方便了跨域認(rèn)證。但需要注意的是,JWT的安全性依賴于秘鑰的安全保管以及對JWT過期時間等的合理設(shè)置。
API設(shè)計
這里設(shè)計兩個公共接口和一個受保護(hù)的接口。
API | 描述 |
---|---|
/api/login | 公開接口。用于用戶登錄 |
/api/register | 公開接口。用于用戶注冊 |
/api/admin/user | 保護(hù)接口,需要驗證JWT |
開發(fā)準(zhǔn)備
初始化項目目錄并切換進(jìn)入
mkdir gin-jwt cd gin-jwt
使用go mod
初始化工程
go mod init gin-jwt
安裝依賴
go get -u github.com/gin-gonic/gin go get -u gorm.io/gorm go get -u gorm.io/driver/postgres go get -u github.com/golang-jwt/jwt/v5 go get -u github.com/joho/godotenv go get -u golang.org/x/crypto
創(chuàng)建第一個API
一開始我們可以在項目的根目錄中創(chuàng)建文件main.go
touch main.go
添加以下內(nèi)容
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() public := r.Group("/api") { public.POST("/register", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "data": "test. register api", }) }) } r.Run("0.0.0.0:8000") }
測試運行
go run main.go
客戶端測試。正常的話會有以下輸出
$ curl -X POST http://127.0.0.1:8000/api/register {"data":"test. register api"}
完善register接口
現(xiàn)在register接口已經(jīng)準(zhǔn)備好了,但一般來說我們會把接口業(yè)務(wù)邏輯放在單獨的文件中,而不是和接口定義寫在一塊。
創(chuàng)建一個控制器的包目錄,并添加文件
mkdir controllers touch controllers/auth.go
auth.go
文件內(nèi)容
package controllers import ( "net/http" "github.com/gin-gonic/gin" ) func Register(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "data": "hello, this is register endpoint", }) }
更新main.go
文件
package main import ( "github.com/gin-gonic/gin" "gin-jwt/controllers" ) func main() { r := gin.Default() public := r.Group("/api") { public.POST("/register", controllers.Register) } r.Run("0.0.0.0:8000") }
重新運行測試
go run main.go
客戶端測試
$ curl -X POST http://127.0.0.1:8000/api/register {"data":"hello, this is register endpoint"}
解析register的客戶端請求
客戶端請求register api需要攜帶用戶名和密碼的參數(shù),服務(wù)端對此做解析。編輯文件controllers/auth.go
package controllers import ( "net/http" "github.com/gin-gonic/gin" ) // /api/register的請求體 type ReqRegister struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } func Register(c *gin.Context) { var req ReqRegister if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "data": req, }) }
客戶端請求測試
$ curl -X POST http://127.0.0.1:8000/api/register -d '{"username": "zhangsan", "password": "123456"}' -H 'Content-Type=application/json' {"data":{"username":"zhangsan","password":"123456"}}
連接關(guān)系型數(shù)據(jù)庫
一般會將數(shù)據(jù)保存到專門的數(shù)據(jù)庫中,這里用PostgreSQL來存儲數(shù)據(jù)。Postgres使用docker來安裝。安裝完postgres后,創(chuàng)建用戶和數(shù)據(jù)庫:
create user ginjwt encrypted password 'ginjwt'; create database ginjwt owner = ginjwt;
創(chuàng)建目錄models
,這個目錄將包含連接數(shù)據(jù)庫和數(shù)據(jù)模型的代碼。
mkdir models
編輯文件models/setup.go
package models import ( "fmt" "log" "os" "github.com/joho/godotenv" "gorm.io/driver/postgres" "gorm.io/gorm" ) var DB *gorm.DB func ConnectDatabase() { err := godotenv.Load(".env") if err != nil { log.Fatalf("Error loading .env file. %v\n", err) } // DbDriver := os.Getenv("DB_DRIVER") DbHost := os.Getenv("DB_HOST") DbPort := os.Getenv("DB_PORT") DbUser := os.Getenv("DB_USER") DbPass := os.Getenv("DB_PASS") DbName := os.Getenv("DB_NAME") dsn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai password=%s", DbHost, DbPort, DbUser, DbName, DbPass) DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatalf("Connect to database failed, %v\n", err) } else { log.Printf("Connect to database success, host: %s, port: %s, user: %s, dbname: %s\n", DbHost, DbPort, DbUser, DbName) } // 遷移數(shù)據(jù)表 DB.AutoMigrate(&User{}) }
新建并編輯環(huán)境配置文件.env
DB_HOST=127.0.0.1 DB_PORT=5432 DB_USER=ginjwt DB_PASS=ginjwt DB_NAME=ginjwt
創(chuàng)建用戶模型,編輯代碼文件models/user.go
package models import ( "html" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type User struct { gorm.Model Username string `gorm:"size:255;not null;unique" json:"username"` Password string `gorm:"size:255;not null;" json:"password"` } func (u *User) SaveUser() (*User, error) { err := DB.Create(&u).Error if err != nil { return &User{}, err } return u, nil } // 使用gorm的hook在保存密碼前對密碼進(jìn)行hash func (u *User) BeforeSave(tx *gorm.DB) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) if err != nil { return err } u.Password = string(hashedPassword) u.Username = html.EscapeString(strings.TrimSpace(u.Username)) return nil }
更新main.go
package main import ( "github.com/gin-gonic/gin" "gin-jwt/controllers" "gin-jwt/models" ) func init() { models.ConnectDatabase() } func main() { r := gin.Default() public := r.Group("/api") { public.POST("/register", controllers.Register) } r.Run("0.0.0.0:8000") }
更新controllers/auth.go
package controllers import ( "net/http" "gin-jwt/models" "github.com/gin-gonic/gin" ) // /api/register的請求體 type ReqRegister struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } func Register(c *gin.Context) { var req ReqRegister if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } u := models.User{ Username: req.Username, Password: req.Password, } _, err := u.SaveUser() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "register success", "data": req, }) }
重新運行服務(wù)端后,客戶端測試
$ curl -X POST http://127.0.0.1:8000/api/register -d '{"username": "zhangsan", "password": "123456"}' -H 'Content-Type=application/json' {"data":{"username":"zhangsan","password":"123456"},"message":"register success"}
添加login接口
登錄接口實現(xiàn)的也非常簡單,只需要提供用戶名和密碼參數(shù)。服務(wù)端接收到客戶端的請求后到數(shù)據(jù)庫中去匹配,確認(rèn)用戶是否存在和密碼是否正確。如果驗證通過則返回一個token,否則返回異常響應(yīng)。
首先在main.go
中注冊API
// xxx func main() { // xxx r := gin.Default() public := r.Group("/api") { public.POST("/register", controllers.Register) public.POST("/login", controllers.Login) } }
在auth.go
中添加Login控制器函數(shù)
// api/login 的請求體 type ReqLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } func Login(c *gin.Context) { var req ReqLogin if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } u := models.User{ Username: req.Username, Password: req.Password, } // 調(diào)用 models.LoginCheck 對用戶名和密碼進(jìn)行驗證 token, err := models.LoginCheck(u.Username, u.Password) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "username or password is incorrect.", }) return } c.JSON(http.StatusOK, gin.H{ "token": token, }) }
LoginCheck
方法在models/user.go
文件中實現(xiàn)
package models import ( "gin-jwt/utils/token" "html" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) func VerifyPassword(password, hashedPassword string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } func LoginCheck(username, password string) (string, error) { var err error u := User{} err = DB.Model(User{}).Where("username = ?", username).Take(&u).Error if err != nil { return "", err } err = VerifyPassword(password, u.Password) if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { return "", err } token, err := token.GenerateToken(u.ID) if err != nil { return "", err } return token, nil }
這里將token相關(guān)的函數(shù)放到了單獨的模塊中,新增相關(guān)目錄并編輯文件
mkdir -p utils/token touch utils/token/token.go
以下代碼為token.go
的內(nèi)容,包含的幾個函數(shù)在后面會用到
package token import ( "fmt" "os" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) func GenerateToken(user_id uint) (string, error) { token_lifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN")) if err != nil { return "", err } claims := jwt.MapClaims{} claims["authorized"] = true claims["user_id"] = user_id claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix() token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(os.Getenv("API_SECRET"))) } func TokenValid(c *gin.Context) error { tokenString := ExtractToken(c) fmt.Println(tokenString) _, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("API_SECRET")), nil }) if err != nil { return err } return nil } // 從請求頭中獲取token func ExtractToken(c *gin.Context) string { bearerToken := c.GetHeader("Authorization") if len(strings.Split(bearerToken, " ")) == 2 { return strings.Split(bearerToken, " ")[1] } return "" } // 從jwt中解析出user_id func ExtractTokenID(c *gin.Context) (uint, error) { tokenString := ExtractToken(c) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("API_SECRET")), nil }) if err != nil { return 0, err } claims, ok := token.Claims.(jwt.MapClaims) // 如果jwt有效,將user_id轉(zhuǎn)換為浮點數(shù)字符串,然后再轉(zhuǎn)換為 uint32 if ok && token.Valid { uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32) if err != nil { return 0, err } return uint(uid), nil } return 0, nil }
在.env
文件中添加兩個環(huán)境變量的配置。TOKEN_HOUR_LIFESPAN
設(shè)置token的過期時長,API_SECRET
是jwt的密鑰。
TOKEN_HOUR_LIFESPAN=1 API_SECRET="wP3-sN6&gG4-lV8>gJ9)"
測試,這里改用python代碼進(jìn)行測試
import requests import json headers = { "Content-Type": "application/json", } resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers) def register(username: str, password: str): req_body = { "username": username, "password": password, } resp = requests.post("http://127.0.0.1:8000/api/register", data=json.dumps(req_body), headers=headers) print(resp.text) def login(username: str, password: str): req_body = { "username": username, "password": password, } resp = requests.post("http://127.0.0.1:8000/api/login", data=json.dumps(req_body), headers=headers) print(resp.text) if resp.status_code == 200: return resp.json()["token"] else: return "" if __name__ == "__main__": username = "lisi" password = "123456" register(username, password) token = login(username, password) print(token)
創(chuàng)建JWT認(rèn)證中間件
創(chuàng)建中間件目錄和代碼文件
mkdir middlewares touch middlewares/middlewares.go
內(nèi)容如下
package middlewares import ( "gin-jwt/utils/token" "net/http" "github.com/gin-gonic/gin" ) func JwtAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { err := token.TokenValid(c) if err != nil { c.String(http.StatusUnauthorized, err.Error()) c.Abort() return } c.Next() } }
在main.go
文件中注冊路由的時候使用中間件
func main() { models.ConnectDatabase() r := gin.Default() public := r.Group("/api") { public.POST("/register", controllers.Register) public.POST("/login", controllers.Login) } protected := r.Group("/api/admin") { protected.Use(middlewares.JwtAuthMiddleware()) protected.GET("/user", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "success", "message": "authorized", }) }) } r.Run("0.0.0.0:8000") }
在controllers/auth.go
文件中實現(xiàn)CurrentUser
func CurrentUser(c *gin.Context) { // 從token中解析出user_id user_id, err := token.ExtractTokenID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } // 根據(jù)user_id從數(shù)據(jù)庫查詢數(shù)據(jù) u, err := models.GetUserByID(user_id) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "success", "data": u, }) }
在models/user.go
文件中實現(xiàn)GetUserByID
// 返回前將用戶密碼置空 func (u *User) PrepareGive() { u.Password = "" } func GetUserByID(uid uint) (User, error) { var u User if err := DB.First(&u, uid).Error; err != nil { return u, errors.New("user not found") } u.PrepareGive() return u, nil }
至此,一個簡單的gin-jwt應(yīng)用就完成了。
客戶端測試python腳本
服務(wù)端的三個接口這里用python腳本來測試
import requests import json headers = { # "Authorization": f"Bearer {token}", "Content-Type": "application/json", } resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers) def register(username: str, password: str): req_body = { "username": username, "password": password, } resp = requests.post("http://127.0.0.1:8000/api/register", data=json.dumps(req_body), headers=headers) print(resp.text) def login(username: str, password: str): req_body = { "username": username, "password": password, } resp = requests.post("http://127.0.0.1:8000/api/login", data=json.dumps(req_body), headers=headers) print(resp.text) if resp.status_code == 200: return resp.json()["token"] else: return "" def test_protect_api(token: str): global headers headers["Authorization"] = f"Bearer {token}" resp = requests.get("http://127.0.0.1:8000/api/admin/user", headers=headers) print(resp.text) if __name__ == "__main__": username = "lisi" password = "123456" register(username, password) token = login(username, password) test_protect_api(token)
運行腳本結(jié)果
{"message":"register success"}
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdXRob3JpemVkIjp0cnVlLCJleHAiOjE3MTk5NDA0NjAsInVzZXJfaWQiOjZ9.qkzn0Ot9hAb54l3RFbGUohHJ9oezGia5x_oXppbD2jQ"}
{"data":{"ID":6,"CreatedAt":"2024-07-03T00:14:20.187725+08:00","UpdatedAt":"2024-07-03T00:14:20.187725+08:00","DeletedAt":null,"username":"wangwu","password":""},"message":"success"}
完整示例代碼
目錄結(jié)構(gòu)
├── client.py # 客戶端測試腳本 ├── controllers # 控制器相關(guān)包 │ └── auth.go # 控制器方法實現(xiàn) ├── gin-jwt.bin # 編譯的二進(jìn)制文件 ├── go.mod # go 項目文件 ├── go.sum # go 項目文件 ├── main.go # 程序入口文件 ├── middlewares # 中間件相關(guān)包 │ └── middlewares.go # 中間件代碼文件 ├── models # 存儲層相關(guān)包 │ ├── setup.go # 配置數(shù)據(jù)庫連接 │ └── user.go # user模塊相關(guān)數(shù)據(jù)交互的代碼文件 ├── README.md # git repo的描述文件 └── utils # 工具類包 └── token # token相關(guān)工具類包 └── token.go # token工具的代碼文件
main.go
package main import ( "log" "github.com/gin-gonic/gin" "gin-jwt/controllers" "gin-jwt/middlewares" "gin-jwt/models" "github.com/joho/godotenv" ) func init() { err := godotenv.Load(".env") if err != nil { log.Fatalf("Error loading .env file. %v\n", err) } } func main() { models.ConnectDatabase() r := gin.Default() public := r.Group("/api") { public.POST("/register", controllers.Register) public.POST("/login", controllers.Login) } protected := r.Group("/api/admin") { protected.Use(middlewares.JwtAuthMiddleware()) // 在路由組中使用中間件 protected.GET("/user", controllers.CurrentUser) } r.Run("0.0.0.0:8000") }
controllers
- auth.go
package controllers import ( "net/http" "gin-jwt/models" "gin-jwt/utils/token" "github.com/gin-gonic/gin" ) // /api/register的請求體 type ReqRegister struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } // api/login 的請求體 type ReqLogin struct { Username string `json:"username" binding:"required"` Password string `json:"password" binding:"required"` } func Login(c *gin.Context) { var req ReqLogin if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } u := models.User{ Username: req.Username, Password: req.Password, } // 調(diào)用 models.LoginCheck 對用戶名和密碼進(jìn)行驗證 token, err := models.LoginCheck(u.Username, u.Password) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": "username or password is incorrect.", }) return } c.JSON(http.StatusOK, gin.H{ "token": token, }) } func Register(c *gin.Context) { var req ReqRegister if err := c.ShouldBindBodyWithJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } u := models.User{ Username: req.Username, Password: req.Password, } _, err := u.SaveUser() if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "data": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "register success", }) } func CurrentUser(c *gin.Context) { // 從token中解析出user_id user_id, err := token.ExtractTokenID(c) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } // 根據(jù)user_id從數(shù)據(jù)庫查詢數(shù)據(jù) u, err := models.GetUserByID(user_id) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "error": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ "message": "success", "data": u, }) }
models
- setup.go
package models import ( "fmt" "log" "os" "gorm.io/driver/postgres" "gorm.io/gorm" ) var DB *gorm.DB func ConnectDatabase() { var err error DbHost := os.Getenv("DB_HOST") DbPort := os.Getenv("DB_PORT") DbUser := os.Getenv("DB_USER") DbPass := os.Getenv("DB_PASS") DbName := os.Getenv("DB_NAME") dsn := fmt.Sprintf("host=%s port=%s user=%s dbname=%s sslmode=disable TimeZone=Asia/Shanghai password=%s", DbHost, DbPort, DbUser, DbName, DbPass) DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { log.Fatalf("Connect to database failed, %v\n", err) } else { log.Printf("Connect to database success, host: %s, port: %s, user: %s, dbname: %s\n", DbHost, DbPort, DbUser, DbName) } // 遷移數(shù)據(jù)表 DB.AutoMigrate(&User{}) }
- user.go
package models import ( "errors" "gin-jwt/utils/token" "html" "strings" "golang.org/x/crypto/bcrypt" "gorm.io/gorm" ) type User struct { gorm.Model Username string `gorm:"size:255;not null;unique" json:"username"` Password string `gorm:"size:255;not null;" json:"password"` } func (u *User) SaveUser() (*User, error) { err := DB.Create(&u).Error if err != nil { return &User{}, err } return u, nil } // 使用gorm的hook在保存密碼前對密碼進(jìn)行hash func (u *User) BeforeSave(tx *gorm.DB) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost) if err != nil { return err } u.Password = string(hashedPassword) u.Username = html.EscapeString(strings.TrimSpace(u.Username)) return nil } // 返回前將用戶密碼置空 func (u *User) PrepareGive() { u.Password = "" } // 對哈希加密的密碼進(jìn)行比對校驗 func VerifyPassword(password, hashedPassword string) error { return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) } func LoginCheck(username, password string) (string, error) { var err error u := User{} err = DB.Model(User{}).Where("username = ?", username).Take(&u).Error if err != nil { return "", err } err = VerifyPassword(password, u.Password) if err != nil && err == bcrypt.ErrMismatchedHashAndPassword { return "", err } token, err := token.GenerateToken(u.ID) if err != nil { return "", err } return token, nil } func GetUserByID(uid uint) (User, error) { var u User if err := DB.First(&u, uid).Error; err != nil { return u, errors.New("user not found") } u.PrepareGive() return u, nil }
utils
- token/token.go
package token import ( "fmt" "os" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) func GenerateToken(user_id uint) (string, error) { token_lifespan, err := strconv.Atoi(os.Getenv("TOKEN_HOUR_LIFESPAN")) if err != nil { return "", err } claims := jwt.MapClaims{} claims["authorized"] = true claims["user_id"] = user_id claims["exp"] = time.Now().Add(time.Hour * time.Duration(token_lifespan)).Unix() token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(os.Getenv("API_SECRET"))) } func TokenValid(c *gin.Context) error { tokenString := ExtractToken(c) fmt.Println(tokenString) _, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("API_SECRET")), nil }) if err != nil { return err } return nil } // 從請求頭中獲取token func ExtractToken(c *gin.Context) string { bearerToken := c.GetHeader("Authorization") if len(strings.Split(bearerToken, " ")) == 2 { return strings.Split(bearerToken, " ")[1] } return "" } // 從jwt中解析出user_id func ExtractTokenID(c *gin.Context) (uint, error) { tokenString := ExtractToken(c) token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return []byte(os.Getenv("API_SECRET")), nil }) if err != nil { return 0, err } claims, ok := token.Claims.(jwt.MapClaims) // 如果jwt有效,將user_id轉(zhuǎn)換為浮點數(shù)字符串,然后再轉(zhuǎn)換為 uint32 if ok && token.Valid { uid, err := strconv.ParseUint(fmt.Sprintf("%.0f", claims["user_id"]), 10, 32) if err != nil { return 0, err } return uint(uid), nil } return 0, nil }
middlewares
- middlewares.go
package middlewares import ( "gin-jwt/utils/token" "net/http" "github.com/gin-gonic/gin" ) func JwtAuthMiddleware() gin.HandlerFunc { return func(c *gin.Context) { err := token.TokenValid(c) if err != nil { c.String(http.StatusUnauthorized, err.Error()) c.Abort() return } c.Next() } }
參考
到此這篇關(guān)于golang 在Gin框架中使用JWT鑒權(quán)的文章就介紹到這了,更多相關(guān)golang Gin框架使用JWT鑒權(quán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang通道阻塞情況與通道無阻塞實現(xiàn)小結(jié)
本文主要介紹了Golang通道阻塞情況與通道無阻塞實現(xiàn)小結(jié),詳細(xì)解析了通道的類型、操作方法以及垃圾回收機(jī)制,從基礎(chǔ)概念到高級應(yīng)用,具有一定的參考價值,感興趣的可以了解一下2024-03-03golang常用庫之字段參數(shù)驗證庫-validator使用詳解
這篇文章主要介紹了golang常用庫:字段參數(shù)驗證庫-validator使用,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借價值,需要的朋友可以參考下2020-10-10Go語言之io.ReadAtLeast函數(shù)的基本使用和原理解析
io.ReadAtLeast函數(shù)是Go語言標(biāo)準(zhǔn)庫提供的一個工具函數(shù),能夠從數(shù)據(jù)源讀取至少指定數(shù)量的字節(jié)數(shù)據(jù)到緩沖區(qū)中,這篇文章主要介紹了io.ReadAtLeast函數(shù)的相關(guān)知識,需要的朋友可以參考下2023-07-07