今天学习的知识点:

  • Gin middleware
  • c.Next()
  • c.Abort()
  • 日志中间件
  • 认证中间件
  • JWT基础
  • 把用户信息放进context

Gin middleware

Gin中间件的底层原理是:责任链与洋葱模型。在Spring MVC中,拦截器(interceptor)依赖的是反射和多层代理,而Go Gin中,中间件的本质是一个函数数组的顺序执行。

Gin的核心数据结构gin.Context中,包含了一个中间件和业务处理函数的切片:

1
2
3
4
5
type Context struct {
// ....
handlers HandlersChain // 本质是[]HandlerFunc,即函数切片
index int8 // 当前执行了第几个函数
}

当你注册了中间件,并写了业务路由时,Gin会把他们按顺序拼接成一条流水线。例如:[Logger,Auth, TargetBusinessFunc]

此时index的初始值是-1

c.Next()

Gin中的c.Next()的作用是:继续执行后续middleware和最终handler。它前面的代码类似迁至逻辑,后面的代码类似后置逻辑,常用于日志记录、耗时统计、链路追踪等。

下面是Next()的代码:

1
2
3
4
5
6
7
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c)
c.index++
}
}

原理解析:

c.Next()只是手动递增了index,并用一个for循环继续往后执行切片里的函数。

  • 如果你在中间件里不写c.Next():当前中间件函数执行完后,主流程的for循环以来会自动推进index,继续执行后面的中间件。
  • 如果你在中间件里写了c.Next(): 会提前出发后面的中间件执行。当后面的中间件和业务函数全部执行完毕弹栈后,代码会回到c.Next()之后的部分继续执行。(洋葱模型)

下面通过一个例子日志中间件来说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func LoggerMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()

path := c.Request.URL.Path
method := c.Request.Method

c.Next()

duration := time.Since(start)
status := c.Writer.Status()

log.Printf("%s %s %d %s", method, path, status, duration)
}
}

将原来main()方法修改为:

1
2
3
r := gin.New()
r.Use(gin.Recovery())
r.Use(LoggerMiddleware())

启动后,随便请求一个地址,此时就会在控制台输出如下内容:

1
2026/06/11 08:51:41 GET /admin/users 200 0s

由此看出,中间件的本质就是

1
type HandlerFunc func(* gin.Context)

c.Next() 前面的代码在请求进入时执行

c.Next()后面的代码在后续handler执行玩抽后执行。

c.Abort()

下面通过一个例子来说明:

1
2
3
4
5
6
7
8
9
10
11
12
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
//使用刚才定义的响应
response.Unauthorized(c, "missing token")
c.Abort()
return
}
c.Next()
}
}

使用:在main()中新增:

1
2
3
4
5
admin := r.Group("/admin")
admin.Use(AuthMiddleware())
{
admin.GET("/users", listUser)
}

请求:

1
GET http://localhost:8080/admin/users

因为在Header中并没有设置Authorization,所以会提示如下:

1
2
3
4
{
"code": "UNAUTHORIZED",
"message": "missing token"
}

Header中设置Authorization属性后,返回数据。

c.Abort()会组织后续handler执行,在认证失败、权限不足、非法请求时常用。

c.Abortreturn的区别:return只是结束当前函数;c.Abort()会告诉Gin不再执行后续handler。实际中通常两者一起用;先写响应,再c.Abort(),然后return

JWT认证中间件

JWT是比较常用的身份验证中间件,下面通过一个例子来实际编码体验一下:

安装jwt模块

1
go get -u github.com/golang-jwt/jwt/v5

pkg/utils/jwt.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
package utils

import (
"errors"
"time"

"github.com/golang-jwt/jwt/v5"
)

var JWTSecret = []byte("your_secret")

type MyCustomClaims struct {
ID int64 `json:"id"`
UserName string `json:"username"`
jwt.RegisteredClaims // 内置的标准字段(过期时间、发行人等)
}

// 生成 Token
func GenerateToken(id int64, username string) (string, error) {
claims := MyCustomClaims{
ID: id,
UserName: username,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(2 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
// 使用HS256算法创建token对象
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 使用密钥签名获得完整token
return token.SignedString(JWTSecret)
}

func ParseToken(tokenstr string) (*MyCustomClaims, error) {
token, err := jwt.ParseWithClaims(tokenstr, &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return JWTSecret, nil
})

if err != nil {
return nil, err
}

if claims, ok := token.Claims.(*MyCustomClaims); ok && token.Valid {
return claims, nil
}

return nil, errors.New("invalid token")
}

使用方式,新建一个中间件:AuthMiddleware,我把它放在middleware/handlers.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
package middleware

import (
"log"
"strings"
"time"

"dev.net.cn/goweb/pkg/utils"
"dev.net.cn/goweb/response"
"github.com/gin-gonic/gin"
)

func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
response.Unauthorized(c, "missing token")
c.Abort()
return
}

parts := strings.SplitN(token, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
response.Unauthorized(c, "invalid token")
c.Abort()
return
}

claims, err := utils.ParseToken(parts[1])
if err != nil {
response.Unauthorized(c, "invalid token")
c.Abort()
return
}
c.Set("currentUserID", claims.ID)
c.Set("currentUsername", claims.UserName)
//c.Set("claims", claims)
c.Next()
}
}

然后在r.Group("/api/d2").Use(middleware.AuthMiddleware())使用它。

1
2
3
4
5
// 访问该链路需要token认证
d2Group := r.Group("/api/d2").Use(middleware.AuthMiddleware())
{
d2Group.GET("/auth", controllers.GetUserListHandler)
}

c.Set("currentUserID",claims.ID) 可以把认证结果放进Gin Context,后续的handler可以c.Get("currentUserID")获取它。