最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Golang 在gin框架中如何使用JWT鑒權(quán)

 更新時間:2024年07月03日 09:35:08   作者:花酒鋤作田  
JWT,全稱 JSON Web Token,是一種開放標(biāo)準(zhǔn)(RFC 7519),用于安全地在雙方之間傳遞信息,這篇文章主要介紹了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")
}

測試運(yùn)行

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ù)邏輯放在單獨(dú)的文件中,而不是和接口定義寫在一塊。

創(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")
}

重新運(yùn)行測試

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

重新運(yùn)行服務(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ù)放到了單獨(dú)的模塊中,新增相關(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)

運(yùn)行腳本結(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中單機(jī)鎖的具體實現(xiàn)詳解

    golang中單機(jī)鎖的具體實現(xiàn)詳解

    這篇文章主要為大家詳細(xì)介紹了golang中單機(jī)鎖的具體實現(xiàn)的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-03-03
  • Go?error的使用方式詳解

    Go?error的使用方式詳解

    當(dāng)我們需要在Go項目中設(shè)計error,就不得不先知道Go error幾種常用方法,今天通過本文給大家介紹Go?error的使用方式詳解,感興趣的朋友一起看看吧
    2022-05-05
  • Go通用的?MapReduce?工具函數(shù)詳解

    Go通用的?MapReduce?工具函數(shù)詳解

    本文介紹了使用Go語言實現(xiàn)的MapReduce框架,特別是在AWSS3?SDK的MultiPartUpload功能中的應(yīng)用,包括并發(fā)上傳和錯誤處理策略,詳細(xì)解釋了如何通過并發(fā)goroutines提高上傳效率,并通過MapReduce模型優(yōu)化代碼結(jié)構(gòu)和處理流程,感興趣的朋友跟隨小編一起看看吧
    2024-09-09
  • 重學(xué)Go語言之基礎(chǔ)數(shù)據(jù)類型詳解

    重學(xué)Go語言之基礎(chǔ)數(shù)據(jù)類型詳解

    Go語言有非常強(qiáng)大的數(shù)據(jù)類型系統(tǒng),其支持的數(shù)據(jù)類型大體上可分為四類:基礎(chǔ)數(shù)據(jù)類型、引用數(shù)據(jù)類型、接口類型、復(fù)合類型。本文就來講講它們各自的用法吧
    2023-02-02
  • Go 語言中的命令行參數(shù)操作詳解

    Go 語言中的命令行參數(shù)操作詳解

    本文介紹了Go語言處理命令行參數(shù)的三種方法,包括os.Args, flag包, Cobra庫,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-10-10
  • Go數(shù)據(jù)庫遷移的實現(xiàn)步驟

    Go數(shù)據(jù)庫遷移的實現(xiàn)步驟

    本文主要介紹了Go數(shù)據(jù)庫遷移的實現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • GPT回答go語言和C語言map操作方法對比

    GPT回答go語言和C語言map操作方法對比

    這篇文章主要為大家介紹了GPT回答go語言和C語言map操作方法對比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-10-10
  • 淺談beego默認(rèn)處理靜態(tài)文件性能低下的問題

    淺談beego默認(rèn)處理靜態(tài)文件性能低下的問題

    下面小編就為大家?guī)硪黄獪\談beego默認(rèn)處理靜態(tài)文件性能低下的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-06-06
  • golang 調(diào)用c語言動態(tài)庫方式實現(xiàn)

    golang 調(diào)用c語言動態(tài)庫方式實現(xiàn)

    本文主要介紹了golang 調(diào)用c語言動態(tài)庫方式實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • 如何在Go語言中高效使用Redis的Pipeline

    如何在Go語言中高效使用Redis的Pipeline

    在 Redis 中,Pipeline 就像一條流水線,它允許我們將多個命令一次性發(fā)送到服務(wù)器,下面我們就來看看如何在Go語言中高效使用Redis的Pipeline吧
    2024-11-11

最新評論

五指山市| 龙井市| 台州市| 吴忠市| 建昌县| 临江市| 太仆寺旗| 婺源县| 灌云县| 天等县| 包头市| 达尔| 芒康县| 稻城县| 九江市| 汉源县| 通州区| 当涂县| 桐梓县| 丹凤县| 青龙| 桑日县| 贺州市| 茂名市| 罗定市| 临邑县| 西乌| 长葛市| 隆尧县| 西乌珠穆沁旗| 鄂托克旗| 广平县| 积石山| 凤城市| 定安县| 收藏| 福贡县| 突泉县| 黄骅市| 新余市| 通许县|