Gin-3 中间件编程及 JWT 身份认证
1. Gin 中间件概述
中间件是处理 HTTP 请求的函数,可以在请求到达路由处理函数之前或之后对请求进行处理。
在 Gin 框架中,中间件常用于处理日志记录、身份验证、权限控制等功能。
go
router := gin.Default()
router.Use(middleware) // 使用中间件
中间件可以通过 Use
方法进行添加,并且可以用于所有路由 或特定路由组。
2. JWT 简介
https://github.com/golang-jwt/jwt
JSON Web Token(JWT)是一种用于认证和授权的标准。JWT 包含三个部分:
- 头部 (Header)
- 载荷 (Payload)
- 签名 (Signature)
text
JWT = Base64UrlEncode(HEADER) + "." + Base64UrlEncode(PAYLOAD) + "." + Base64UrlEncode(SIGNATURE)
JWT 的优点是自包含,它在用户和服务之间传递信息时不依赖于存储。
由于 JWT 包含所有必要的用户信息,服务端不需要保持用户的状态。
3. Gin 中的 JWT 身份认证实现
在 Gin 中实现 JWT 身份认证主要包含以下步骤:
- 生成 JWT Token:登录时生成 JWT Token。
- 验证 JWT Token:通过中间件验证请求中的 Token。
- 访问受保护路由:只有验证通过的用户才能访问受保护的路由。
3.1 JWT 生成与验证函数
通过以下代码,您可以生成和验证 JWT Token:
go
package jwt_plugin
import "github.com/golang-jwt/jwt/v5"
var key = "abcdefg123" // 用于加密和解密的密钥
// 数据结构,存储用户信息和标准声明
type Data struct {
Name string `json:"name"`
Age int `json:"age"`
Gender int `json:"gender"`
jwt.RegisteredClaims
}
// 生成 JWT Token
func Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, data)
sign, err := token.SignedString([]byte(key))
if err != nil {
return "", err
}
return sign, nil
}
// 验证 JWT Token
func Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (any, error) {
return []byte(key), nil
})
return err
}
生成 JWT Token 时,我们会使用 Sign
函数,验证时使用 Verify
函数。
Sign
函数会将用户信息(载荷)和签名一起返回,Verify
函数用于验证 JWT Token 是否有效。
3.2 登录接口实现
用户登录时,通过 Login
函数生成 JWT Token,并返回给客户端。
go
package login
import (
"github.com/gin-gonic/gin"
"golang13-gin/jwt_plugin"
"net/http"
"time"
)
func Login(c *gin.Context) {
// 用户信息及 JWT 载荷
data := jwt_plugin.Data{
Name: "nick",
Age: 18,
Gender: 1,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
},
}
// 生成 JWT Token
sign, err := jwt_plugin.Sign(data)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(),
})
return
}
// 返回 JWT Token
c.JSON(http.StatusOK, gin.H{
"access_token": sign,
})
}
登录成功后,系统会返回一个包含用户信息和有效期的 JWT Token。
3.3 JWT 身份验证中间件
go
package middleware
import (
"github.com/gin-gonic/gin"
"golang13-gin/jwt_plugin"
"net/http"
)
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
accessToken := c.Request.Header.Get("access_token") // 获取请求头中的 Token
data := &jwt_plugin.Data{}
err := jwt_plugin.Verify(accessToken, data) // 验证 Token
if err != nil {
c.JSON(http.StatusForbidden, gin.H{
"error": "身份认证失败",
})
c.Abort() // 中止请求
}
c.Set("auth_info", data) // 将用户信息存储在上下文中
c.Next() // 继续后续处理
}
}
身份验证中间件 Auth
会从请求头中提取 JWT Token 并进行验证。如果验证失败,返回 403 错误;否则将用户信息存储到上下文,供后续路由使用。
4. CORS 中间件
CORS(跨域资源共享)允许服务器指定哪些源(Origin)可以访问资源。以下是设置 CORS 中间件的代码:
go
package middleware
import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func Cors() gin.HandlerFunc {
return cors.New(cors.Config{
AllowAllOrigins: true,
AllowHeaders: []string{
"Origin", "Content-Length", "Content-Type",
},
AllowMethods: []string{
"GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS",
},
})
}
CORS 中间件配置允许来自任何来源的请求,并且可以处理指定的 HTTP 方法和头部。
5. 路由初始化与中间件使用
在 Gin 中,可以通过 Use
方法将中间件添加到路由组中,以下是一个例子:
go
func InitRoutes(r *gin.Engine) {
api := r.Group("/api")
api.Use(middleware.Cors(), middleware.Auth()) // 使用 CORS 和 JWT 验证中间件
InitCourse(api)
InitUser(api)
InitUpload(api)
notAuthApi := r.Group("/api")
notAuthApi.Use(middleware.Cors())
InitLogin(notAuthApi) // 不需要身份验证的路由
}
总结
概念 | 描述 | 代码示例 |
---|---|---|
Gin 中间件 | 中间件是一个函数,用来处理 HTTP 请求。在请求进入路由处理函数之前或之后,执行某些操作。 | router.Use(middleware) |
JWT (JSON Web Token) | 一种用于认证和授权的标准格式,通过三个部分组成:头部、载荷和签名。它具有自包含特性,可以在分布式系统中使用。 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, data) |
JWT 生成 | 通过载荷和密钥生成 JWT Token。 | sign, err := jwt_plugin.Sign(data) |
JWT 验证 | 通过 Token 验证用户身份,确保请求的合法性。 | err := jwt_plugin.Verify(accessToken, data) |
身份认证中间件 | 在请求到达目标路由之前,验证请求中的 JWT Token,确保只有通过验证的请求才能继续处理。 | func Auth() gin.HandlerFunc { return func(c *gin.Context) { ... } } |
CORS 中间件 | 解决浏览器跨域问题,允许不同域的请求访问服务器资源。 | func Cors() gin.HandlerFunc { return cors.New(cors.Config{ AllowAllOrigins: true }) } |
路由组 | 将具有共同前缀的路由放在一个组中,方便统一管理和中间件的应用。 | api := r.Group("/api") |
JWT 载荷 | JWT 中的数据部分,包含用户信息和其他元数据。可以自定义内容,也可以使用预定义的注册声明(如过期时间)。 | RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)) } |
路由配置 | 定义与中间件、请求处理函数相关的路由。 | InitLogin(notAuthApi) |
表格解释:
- Gin 中间件 是一类用于在路由处理函数执行前或后处理请求的函数,常见的中间件包括身份认证、中间件控制等。
- JWT 是一种 JSON 格式的认证方式,包含头部、载荷和签名,适用于分布式系统,解决了服务端无状态的问题。
- JWT 生成和验证 是通过特定的密钥和载荷数据生成的,可以通过库函数轻松实现生成和验证操作。
- 身份认证中间件 通过在请求处理中拦截请求并验证 JWT Token 是否有效来确保用户身份的合法性。
- CORS 中间件 允许不同来源的客户端发起请求,解决跨域问题,常见于前后端分离的应用场景。
- 路由组 可以帮助组织和管理具有共同特征的路由,通过给路由组添加中间件,使得多个路由共享特定的功能。
- JWT 载荷 中的数据部分,存储的是用户信息以及与身份认证相关的信息(如过期时间等)。
JWT载荷
JWT 载荷部分是什么,是否可有可无?
JWT 的载荷部分是存储在 Token 中的实际信息,它包含了 声明(claims),这些声明通常用于存储用户信息、权限、Token 的有效期等。
JWT 载荷部分包含以下几种类型的声明:
- 注册声明(Registered Claims):如 exp(过期时间)、iat(签发时间)、sub(主题)等。
- 公共声明(Public Claims):可以自定义,用于表示用户信息或其他数据。
- 私有声明(Private Claims):由双方约定用于传递的信息。
是否可有可无?
如果只是需要传递一个简单的标识符(如用户 ID),那么可以简化载荷部分。
但通常,JWT 的载荷是不可或缺的,因为它包含了 Token 的有效期、权限等重要信息。
如果载荷部分为空或缺少必要的字段,Token 的使用价值将大大降低。