实现Gin框架JWT鉴权的中间件

1,578 阅读3分钟

前言

在现代web应用中,用户授权和验证是至关重要的。为了保护应用程序免受攻击,我们需要利用一些鉴权策略来防止未经授权的访问。而JWT(JSON Web Token)就是一种流行的安全传输令牌技术,用于客户端和服务器之间的安全通信。

在这篇文章中,我们将学习如何使用Gin框架来实现JWT鉴权的中间件。

什么是中间件

在Gin框架中,中间件是一种将HTTP请求传递给下一级处理程序的高级机制。通常,中间件被用来修改或增强HTTP请求或响应,或者是通过中间件来执行诸如鉴权、性能监测、日志记录等常见任务。

JWT鉴权简介

JSON Web Token(JWT)是一种安全的、基于标准化的方式传输信息的方法。JWT通常由三部分组成:

  1. Header:描述令牌的元数据和签名算法,通常为JSON格式。
  2. Payload:令牌的实际信息,通常也是一个JSON格式的对象。
  3. Signature:用于验证令牌有效性的签名信息。

JWT鉴权的实现步骤如下:

  1. 用户向服务器发送请求,请求哪些资源或操作。
  2. 服务器将请求与JWT进行验证。
  3. 如果JWT有效,则允许请求操作。
  4. 如果JWT无效,则拒绝请求。

实现

要实现JWT鉴权中间件,我们需要以下几个部分:

导入包

首先,我们需要导入必要的包。

import (
   "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "net/http"
   "time"
)

JWT结构体

我们需要一个结构体,它将用户数据(例如ID和用户名)与JWT令牌相关联。

type JWTClaims struct {
   ID       uint   `json:"id"`
   UserName string `json:"user_name"`
   jwt.StandardClaims
}

跳过鉴权

我们需要一个列表,其中包含所有不需要鉴权的路径。

var skipPaths = []string{"/login"}

JWT中间件

接下来,我们需要定义JWT中间件。

func JWTAuthMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      for _, path := range skipPaths {
         if path == c.FullPath() {
            c.Next()
            return
         }
      }

      tokenString := c.Request.Header.Get("Authorization")
      if tokenString == "" {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }

      token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
         return []byte("secret"), nil  // 在这里你应该使用加密的密钥
      })

      if err != nil {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }

      if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
         c.Set("id", claims.ID)
         c.Set("user_name", claims.UserName)
         c.Next()
      } else {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }
   }
}

该中间件将首先检查所请求的路径是否在跳过列表中。如果是,它将放行到下一个处理程序。否则,它将检查请求标头中是否存在JWT令牌。如果没有,它将返回HTTP 401“未授权”状态。否则,它将对该令牌进行验证,并将从中提取出包含的声明。这些信息可以在后续处理程序中使用。

使用

现在,我们已经成功地编写了一个JWT鉴权中间件,我们可以将其用于Gin应用程序中。为了做到这一点,我们将使用一个小演示来验证该中间件是否按预期工作。

func main() {
   r := gin.Default()
   r.Use(JWTAuthMiddleware())

   r.GET("/login", func(c *gin.Context) {
      token := jwt.NewWithClaims(jwt.SigningMethodHS256, JWTClaims{
         ID:       123,
         UserName: "guest",
         StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
         },
      })

      tokenString, err := token.SignedString([]byte("secret"))
      if err != nil {
         c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to create token"})
         return
      }

      c.JSON(http.StatusOK, gin.H{"token": tokenString})
   })

   r.GET("/hello", func(c *gin.Context) {
       userId, idOk := c.Get("id")
       userName, nameOk := c.Get("user_name")
       if !idOk || !nameOk {
           c.JSON(http.StatusOK, gin.H{"msg": "hello world"})
           return
       }
       c.JSON(http.StatusOK, gin.H{
           "id": userId,
           "username": userName,
       })
   })
   
   r.Run(":8080")
}

总结

JWT 身份验证机制是一种安全、快速的解决方案,适用于 Web 开发。使用 Gin 框架的中间件可以轻松地将 JWT 验证添加到应用程序中。通过在 Context 中存储用户 ID,可以在请求处理程序中访问当前用户信息。

完整代码

package main

import (
   "github.com/dgrijalva/jwt-go"
   "github.com/gin-gonic/gin"
   "net/http"
   "time"
)

type JWTClaims struct {
   ID       uint   `json:"id"`
   UserName string `json:"user_name"`
   jwt.StandardClaims
}

var skipPaths = []string{"/login"}

func JWTAuthMiddleware() gin.HandlerFunc {
   return func(c *gin.Context) {
      for _, path := range skipPaths {
         if path == c.FullPath() {
            c.Next()
            return
         }
      }

      tokenString := c.Request.Header.Get("Authorization")
      if tokenString == "" {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }

      token, err := jwt.ParseWithClaims(tokenString, &JWTClaims{}, func(token *jwt.Token) (interface{}, error) {
         return []byte("secret"), nil // 在这里你应该使用加密的密钥
      })

      if err != nil {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }

      if claims, ok := token.Claims.(*JWTClaims); ok && token.Valid {
         c.Set("id", claims.ID)
         c.Set("user_name", claims.UserName)
         c.Next()
      } else {
         c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
         return
      }
   }
}

func main() {
   r := gin.Default()
   r.Use(JWTAuthMiddleware())

   r.GET("/login", func(c *gin.Context) {
      token := jwt.NewWithClaims(jwt.SigningMethodHS256, JWTClaims{
         ID:       123,
         UserName: "guest",
         StandardClaims: jwt.StandardClaims{
            ExpiresAt: time.Now().Add(time.Hour * 24).Unix(),
         },
      })

      tokenString, err := token.SignedString([]byte("secret"))
      if err != nil {
         c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to create token"})
         return
      }

      c.JSON(http.StatusOK, gin.H{"token": tokenString})
   })

   r.GET("/hello", func(c *gin.Context) {
      userId, idOk := c.Get("id")
      userName, nameOk := c.Get("user_name")
      if !idOk || !nameOk {
         c.JSON(http.StatusOK, gin.H{"msg": "hello world"})
         return
      }
      c.JSON(http.StatusOK, gin.H{
         "id":       userId,
         "username": userName,
      })
   })

   r.Run(":8080")
}