前言
在现代web应用中,用户授权和验证是至关重要的。为了保护应用程序免受攻击,我们需要利用一些鉴权策略来防止未经授权的访问。而JWT(JSON Web Token)就是一种流行的安全传输令牌技术,用于客户端和服务器之间的安全通信。
在这篇文章中,我们将学习如何使用Gin框架来实现JWT鉴权的中间件。
什么是中间件
在Gin框架中,中间件是一种将HTTP请求传递给下一级处理程序的高级机制。通常,中间件被用来修改或增强HTTP请求或响应,或者是通过中间件来执行诸如鉴权、性能监测、日志记录等常见任务。
JWT鉴权简介
JSON Web Token(JWT)是一种安全的、基于标准化的方式传输信息的方法。JWT通常由三部分组成:
- Header:描述令牌的元数据和签名算法,通常为JSON格式。
- Payload:令牌的实际信息,通常也是一个JSON格式的对象。
- Signature:用于验证令牌有效性的签名信息。
JWT鉴权的实现步骤如下:
- 用户向服务器发送请求,请求哪些资源或操作。
- 服务器将请求与JWT进行验证。
- 如果JWT有效,则允许请求操作。
- 如果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")
}