使用 Redis 实现 RBAC 权限管理
1. 什么是 RBAC?
RBAC(Role-Based Access Control,基于角色的访问控制)是一种常见的权限管理模型,它通过用户(User)、角色(Role)、权限(Permission) 及其映射关系来控制访问权限。RBAC 的基本思路是:
- 用户被分配一个或多个角色;
- 每个角色拥有一定的权限;
- 通过用户所属角色来决定其是否有权限访问某个资源。
2. 为什么使用 Redis 实现 RBAC?
在传统的 RBAC 设计中,权限数据通常存储在 数据库(如 MySQL),但这种方式可能存在以下问题:
- 查询性能低:每次鉴权都需要查询多张表,影响 API 响应速度;
- 不适用于高并发:数据库连接池有限,在高并发场景下可能成为瓶颈;
- 权限变更不灵活:数据库方案通常需要定期同步缓存,否则变更不会立即生效。
使用 Redis 作为 RBAC 权限存储的优势:
- 高性能:Redis 作为内存数据库,查询速度极快;
- 低延迟:可以直接
O(1)
查询权限数据,而无需复杂的 SQL 语句; - 支持动态权限变更:用户权限变更可以实时生效,而不需要等待数据库更新;
- 适用于分布式系统:多台服务器可以共享 Redis 权限数据,避免不同实例状态不一致的问题。
3. 设计 RBAC 数据结构
我们使用 Redis 作为权限存储,并设计以下 Key 结构:
Key | Value | 说明 |
---|---|---|
user_roles:{user_id} | ["admin", "editor"] | 用户的角色列表 |
role_permissions:{role} | ["read", "write", "delete"] | 角色的权限列表 |
permission_routes:{permission} | ["GET:/users", "POST:/articles"] | 权限对应的 API |
blacklist_tokens | 存储已注销的 Token | 使 JWT 失效,支持主动登出 |
4. 代码实现
我们使用 Gin 作为 Web 框架,并结合 Redis 进行权限管理。
📌 4.1 安装依赖
go get -u github.com/gin-gonic/gin
go get -u github.com/golang-jwt/jwt/v5
go get -u github.com/redis/go-redis/v9
📌 4.2 初始化 Redis
package main
import (
"context"
"fmt"
"log"
"github.com/redis/go-redis/v9"
)
// 初始化 Redis 客户端
var ctx = context.Background()
var redisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379", // 连接 Redis
})
// 初始化 RBAC 角色 & 权限映射
func setupRBAC() {
// 角色 → 权限
redisClient.SAdd(ctx, "role_permissions:admin", "read", "write", "delete")
redisClient.SAdd(ctx, "role_permissions:editor", "read", "write")
redisClient.SAdd(ctx, "role_permissions:viewer", "read")
// 权限 → API
redisClient.SAdd(ctx, "permission_routes:read", "GET:/users", "GET:/articles")
redisClient.SAdd(ctx, "permission_routes:write", "POST:/articles", "PUT:/articles")
redisClient.SAdd(ctx, "permission_routes:delete", "DELETE:/articles")
// 用户 → 角色
redisClient.SAdd(ctx, "user_roles:1", "admin")
redisClient.SAdd(ctx, "user_roles:2", "editor")
redisClient.SAdd(ctx, "user_roles:3", "viewer")
log.Println("RBAC 权限映射初始化完成")
}
📌 4.3 生成 JWT 令牌
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// JWT 密钥
var jwtSecret = []byte("supersecretkey")
// 生成 JWT 令牌
func GenerateJWT(userID int) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": userID,
"exp": time.Now().Add(24 * time.Hour).Unix(), // 24 小时有效
})
return token.SignedString(jwtSecret)
}
// 解析 JWT 令牌
func ParseJWT(tokenString string) (int, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})
if err != nil || !token.Valid {
return 0, fmt.Errorf("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return 0, fmt.Errorf("invalid claims")
}
return int(claims["user_id"].(float64)), nil
}
📌 4.4 鉴权中间件
// 访问权限检查
func hasAccess(userID int, method, path string) bool {
// 1. 获取用户角色
roles, err := redisClient.SMembers(ctx, fmt.Sprintf("user_roles:%d", userID)).Result()
if err != nil || len(roles) == 0 {
return false
}
// 2. 遍历角色,获取权限
for _, role := range roles {
permissions, _ := redisClient.SMembers(ctx, fmt.Sprintf("role_permissions:%s", role)).Result()
for _, permission := range permissions {
routes, _ := redisClient.SMembers(ctx, fmt.Sprintf("permission_routes:%s", permission)).Result()
for _, route := range routes {
if route == fmt.Sprintf("%s:%s", method, path) {
return true
}
}
}
}
return false
}
// RBAC 中间件
func RBACMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.JSON(401, gin.H{"error": "未提供 Token"})
c.Abort()
return
}
// 解析 JWT
userID, err := ParseJWT(tokenString)
if err != nil {
c.JSON(401, gin.H{"error": "Token 无效"})
c.Abort()
return
}
// 检查权限
if !hasAccess(userID, c.Request.Method, c.FullPath()) {
c.JSON(403, gin.H{"error": "无访问权限"})
c.Abort()
return
}
c.Set("userID", userID)
c.Next()
}
}
📌 4.5 API 接口
func main() {
r := gin.Default()
setupRBAC()
// 登录
r.POST("/login", func(c *gin.Context) {
userID := 1 // 假设用户 1 登录
token, _ := GenerateJWT(userID)
c.JSON(200, gin.H{"token": token})
})
// 受保护 API
api := r.Group("/api", RBACMiddleware())
api.GET("/users", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "获取用户列表"})
})
api.POST("/articles", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "创建文章"})
})
api.DELETE("/articles", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "删除文章"})
})
r.Run(":8080")
}
5. 方案总结
✅ Redis 存权限(推荐):高效、适用于分布式
✅ RBAC 权限映射:角色权限映射清晰
✅ JWT 认证:无状态,适用于 API 认证
这样,你就能 用 Redis 设计一套高效的 RBAC 权限管理,并支持 API 映射!🔥