| 
                            
                                  什么是JWTJWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间传递信息。尤其适用于身份验证和授权场景。JWT 的设计允许信息在各方之间安全地、 compactly(紧凑地)传输,因为其自身包含了所有需要的认证信息,从而减少了需要查询数据库或会话存储的需求。 JWT主要由三部分组成,通过.连接: 
	Header(头部):描述JWT的元数据,通常包括类型(通常是JWT)和使用的签名算法(如HS256、RS256等)。Payload(载荷):包含声明(claims),即用户的相关信息。这些信息可以是公开的,也可以是私有的,但应避免放入敏感信息,因为该部分可以被解码查看。载荷中的声明可以验证,但不加密。Signature(签名):用于验证JWT的完整性和来源。它是通过将Header和Payload分别进行Base64编码后,再与一个秘钥(secret)一起通过指定的算法(如HMAC SHA256)计算得出的。 JWT的工作流程大致如下: 
	认证阶段:用户向服务器提供凭证(如用户名和密码)。服务器验证凭证无误后,生成一个JWT,其中包含用户标识符和其他声明,并使用秘钥对其进行签名。使用阶段:客户端收到JWT后,可以在后续的每个请求中将其放在HTTP请求头中发送给服务器,以此证明自己的身份。验证阶段:服务器收到JWT后,会使用相同的秘钥验证JWT的签名,确保其未被篡改,并检查过期时间等其他声明,从而决定是否允许执行请求。 JWT的优势在于它的无状态性,服务器不需要存储会话信息,这减轻了服务器的压力,同时也方便了跨域认证。但需要注意的是,JWT的安全性依赖于秘钥的安全保管以及对JWT过期时间等的合理设置。 API设计这里设计两个公共接口和一个受保护的接口。 
	
		
			| API | 描述 |  
			| /api/login | 公开接口。用于用户登录 |  
			| /api/register | 公开接口。用于用户注册 |  
			| /api/admin/user | 保护接口,需要验证JWT |  开发准备初始化项目目录并切换进入 
	
		
			| 1 2 | mkdir gin-jwt cd gin-jwt |  使用go mod初始化工程 安装依赖 
	
		
			| 1 2 3 4 5 6 | 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 |  创建第一个API一开始我们可以在项目的根目录中创建文件main.go 添加以下内容 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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") } |  测试运行 客户端测试。正常的话会有以下输出 
	
		
			| 1 2 | $ curl -X POST http://127.0.0.1:8000/api/register {"data":"test. register api"} |  完善register接口现在register接口已经准备好了,但一般来说我们会把接口业务逻辑放在单独的文件中,而不是和接口定义写在一块。 创建一个控制器的包目录,并添加文件 
	
		
			| 1 2 | mkdir controllers touch controllers/auth.go |  auth.go文件内容 
	
		
			| 1 2 3 4 5 6 7 8 9 10 | 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文件 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 | 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") } |  重新运行测试 客户端测试 
	
		
			| 1 2 | $ curl -X POST http://127.0.0.1:8000/api/register {"data":"hello, this is register endpoint"} |  解析register的客户端请求客户端请求register api需要携带用户名和密码的参数,服务端对此做解析。编辑文件controllers/auth.go 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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,     }) } |  客户端请求测试 
	
		
			| 1 2 | $ 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"}} |  连接关系型数据库一般会将数据保存到专门的数据库中,这里用PostgreSQL来存储数据。Postgres使用docker来安装。安装完postgres后,创建用户和数据库: 
	
		
			| 1 2 | create user ginjwt encrypted password 'ginjwt'; create database ginjwt owner = ginjwt; |  创建目录models,这个目录将包含连接数据库和数据模型的代码。 编辑文件models/setup.go 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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)     }     // 迁移数据表     DB.AutoMigrate(&User{}) } |  新建并编辑环境配置文件.env 
	
		
			| 1 2 3 4 5 | DB_HOST=127.0.0.1 DB_PORT=5432 DB_USER=ginjwt DB_PASS=ginjwt DB_NAME=ginjwt |  创建用户模型,编辑代码文件models/user.go 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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在保存密码前对密码进行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 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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,     }) } |  重新运行服务端后,客户端测试 
	
		
			| 1 2 | $ 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接口登录接口实现的也非常简单,只需要提供用户名和密码参数。服务端接收到客户端的请求后到数据库中去匹配,确认用户是否存在和密码是否正确。如果验证通过则返回一个token,否则返回异常响应。 首先在main.go中注册API 
	
		
			| 1 2 3 4 5 6 7 8 9 10 | // xxx func main() {     // xxx     r := gin.Default()     public := r.Group("/api")     {         public.POST("/register", controllers.Register)         public.POST("/login", controllers.Login)     } } |  在auth.go中添加Login控制器函数 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // 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,     }     // 调用 models.LoginCheck 对用户名和密码进行验证     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文件中实现 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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相关的函数放到了单独的模块中,新增相关目录并编辑文件 
	
		
			| 1 2 | mkdir -p utils/token touch utils/token/token.go |  以下代码为token.go的内容,包含的几个函数在后面会用到 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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转换为浮点数字符串,然后再转换为 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文件中添加两个环境变量的配置。TOKEN_HOUR_LIFESPAN设置token的过期时长,API_SECRET是jwt的密钥。 
	
		
			| 1 2 | TOKEN_HOUR_LIFESPAN=1 API_SECRET="wP3-sN6&gG4-lV8>gJ9)" |  测试,这里改用python代码进行测试 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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) |  创建JWT认证中间件创建中间件目录和代码文件 
	
		
			| 1 2 | mkdir middlewares touch middlewares/middlewares.go |  内容如下 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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文件中注册路由的时候使用中间件 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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文件中实现CurrentUser 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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     }     // 根据user_id从数据库查询数据     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文件中实现GetUserByID 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 | // 返回前将用户密码置空 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应用就完成了。 客户端测试python脚本服务端的三个接口这里用python脚本来测试 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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) |  运行脚本结果 
{"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"}
 完整示例代码目录结构
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | ├── client.py  # 客户端测试脚本 ├── controllers  # 控制器相关包 │   └── auth.go  # 控制器方法实现 ├── gin-jwt.bin  # 编译的二进制文件 ├── go.mod  # go 项目文件 ├── go.sum  # go 项目文件 ├── main.go  # 程序入口文件 ├── middlewares  # 中间件相关包 │   └── middlewares.go  # 中间件代码文件 ├── models  # 存储层相关包 │   ├── setup.go  # 配置数据库连接 │   └── user.go  # user模块相关数据交互的代码文件 ├── README.md  # git repo的描述文件 └── utils  # 工具类包     └── token  # token相关工具类包         └── token.go  # token工具的代码文件 |  main.go
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | 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,     }     // 调用 models.LoginCheck 对用户名和密码进行验证     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     }     // 根据user_id从数据库查询数据     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
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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)     }     // 迁移数据表     DB.AutoMigrate(&User{}) } |  
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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在保存密码前对密码进行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 = "" } // 对哈希加密的密码进行比对校验 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
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | 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转换为浮点数字符串,然后再转换为 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
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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()     } } |  
 |