Go Gin 框架怎么实现自定义 RBAC 权限控制的 API 鉴权?

文章导读
在 Go Gin 框架中实现自定义 RBAC 权限控制,核心是通过中间件(Middleware)拦截请求,结合 JWT Claims 中的用户角色信息与路由所需的权限进行比对。
📋 目录
  1. 核心代码实现
  2. 路由绑定示例
  3. 验证方法
  4. 常见坑与排查
A A

在 Go Gin 框架中实现自定义 RBAC 权限控制,核心是通过中间件(Middleware)拦截请求,结合 JWT Claims 中的用户角色信息与路由所需的权限进行比对。

先说结论:基于 Gin 中间件机制在请求进入 Handler 前完成角色校验,是耦合度最低且易于维护的方案。

  • 适合:需要区分管理员、普通用户等多角色访问的 API 服务
  • 先看:用户身份标识(如 JWT Claims)中是否已包含角色信息
  • 建议:将权限规则配置在外部文件或数据库中,避免硬编码在代码里

核心代码实现

实现分为两步:首先解析 Token 将角色存入 Context,其次编写中间件校验角色。

Go Gin 框架怎么实现自定义 RBAC 权限控制的 API 鉴权?

1. JWT 解析与角色提取

在鉴权中间件中,验证 Token 有效性后将 roles 写入上下文,避免后续重复解析。以下示例展示如何提取 Claims 并存储。

func AuthMiddleware(secretKey string) gin.HandlerFunc {
    return func(c *gin.Context) {
        tokenStr := c.GetHeader("Authorization")
        if tokenStr == "" {
            c.AbortWithStatusJSON(401, gin.H{"error": "缺失 Token"})
            return
        }
        
        // 去除 Bearer 前缀
        tokenStr = strings.TrimPrefix(tokenStr, "Bearer ")
        
        // 解析 Token (示例伪代码,需引入 github.com/golang-jwt/jwt/v5)
        // token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
        //     return []byte(secretKey), nil
        // })
        // if err != nil || !token.Valid {
        //     c.AbortWithStatusJSON(401, gin.H{"error": "Token 无效"})
        //     return
        // }
        
        // 假设 claims 结构体中包含 Roles 字段
        // claims := token.Claims.(*CustomClaims)
        
        // 关键步骤:将角色存入 Context
        c.Set("roles", claims.Roles) 
        c.Next()
    }
}

2. RBAC 鉴权中间件

从 Context 获取角色列表,遍历比对是否包含所需角色。此处补充完整的比对逻辑。

func RoleAuth(requiredRoles ...string) gin.HandlerFunc {
    return func(c *gin.Context) {
        roles, exists := c.Get("roles")
        if !exists {
            c.AbortWithStatusJSON(401, gin.H{"error": "未登录或角色信息缺失"})
            return
        }
        
        userRoles, ok := roles.([]string)
        if !ok {
            c.AbortWithStatusJSON(500, gin.H{"error": "角色格式错误"})
            return
        }

        // 核心比对逻辑:检查用户角色是否包含任一所需角色
        hasPermission := false
        for _, r := range userRoles {
            for _, required := range requiredRoles {
                if r == required {
                    hasPermission = true
                    break
                }
            }
            if hasPermission {
                break
            }
        }

        if !hasPermission {
            c.AbortWithStatusJSON(403, gin.H{"error": "权限不足"})
            return
        }
        c.Next()
    }
}

路由绑定示例

在初始化 Gin 引擎时,将中间件应用到特定路由组。注意中间件的顺序,先 Auth 后 Role。

Go Gin 框架怎么实现自定义 RBAC 权限控制的 API 鉴权?
r := gin.Default()
// 全局应用 Auth 中间件解析 Token
r.Use(AuthMiddleware("my-secret-key"))

// 定义需要 admin 角色的路由组
adminGroup := r.Group("/api/admin")
adminGroup.Use(RoleAuth("admin"))
{
    adminGroup.GET("/users", GetUsers)
    adminGroup.POST("/users", CreateUser)
}

// 定义需要 admin 或 editor 角色的路由
editorGroup := r.Group("/api/content")
editorGroup.Use(RoleAuth("admin", "editor"))
{
    editorGroup.POST("/article", CreateArticle)
}

验证方法

使用 curl 命令模拟不同场景请求,验证状态码是否符合预期。

场景 1:无 Token 访问

Go Gin 框架怎么实现自定义 RBAC 权限控制的 API 鉴权?
curl -i http://localhost:8080/api/admin/users
# 预期输出:HTTP/1.1 401 Unauthorized

场景 2:普通用户 Token 访问管理员接口

curl -i -H "Authorization: Bearer <user_token>" http://localhost:8080/api/admin/users
# 预期输出:HTTP/1.1 403 Forbidden

场景 3:管理员 Token 访问管理员接口

curl -i -H "Authorization: Bearer <admin_token>" http://localhost:8080/api/admin/users
# 预期输出:HTTP/1.1 200 OK

常见坑与排查

  • Token 有效性优先:必须先验证 Token 签名和过期时间,再校验角色,避免无效 Token 通过角色检查。
  • 角色大小写:建议统一转为小写后再比对,防止 "Admin" 与 "admin" 不一致。
  • Context 键名冲突:确保 c.Set("roles") 的键名在全局唯一,避免被其他中间件覆盖。
  • 性能优化:如果角色数据存储在数据库而非 Token 中,需在中间件内增加 Redis 缓存,避免每次请求查库。