11--RBAC鉴权

在先前我们写了一些在设计中需要管理员权限的API接口,但并没有实现权限组
本章我们将通过实现RBAC权限模型来对用户进行鉴权

一.Role字段

model/user.go我们为User结构体添加成员Role

// User 对应数据库中的 user 表
type User struct {
	Uid       uint      `gorm:"primaryKey;column:uid;autoIncrement" json:"uid"`
	Username  string    `gorm:"column:username;type:varchar(20);not null" json:"username"` // 用户名,唯一且不能为空
	Email     string    `gorm:"column:email;type:varchar(100);uniqueIndex" json:"email"`
	Phone     string    `gorm:"column:phone;type:varchar(20);uniqueIndex" json:"phone"`
	Password  string    `gorm:"column:password;type:varchar(255);not null" json:"-"`    // json:"-" 在接口返回 JSON 时自动隐藏密码
	Role      string    `gorm:"column:role;type:varchar(20);default:user" json:"role"` // (新) admin / user
	CreatedAt time.Time `gorm:"column:created_at;autoCreateTime" json:"created_at"`     // 注册时间,GORM 会在插入时自动填充当前时间
}

Role默认值为user,可在后台通过SQL语句进行修改

二. JWT中的Role信息

我们需要让Token能传递Role信息,以通过JWT进行鉴权

pkg/jwtx/jwt.go

// Claims 定义JWT的自定义声明结构体,包含用户ID和用户名
type Claims struct {
	Uid                  uint   `json:"uid"`      // 用户ID
	Username             string `json:"username"` // 用户名
	Role                 string `json:"role"`     // (新) 权限
	jwt.RegisteredClaims        // 继承标准的JWT注册声明
}

// GenerateToken 生成JWT token,接受用户ID、用户名、密钥和过期时间作为参数,返回生成的JWT字符串或错误
func GenerateToken(uid uint, username, role, secrect string, expire int) (string, error) {
	claims := Claims{
		Uid:      uid,
		Username: username,
		Role:     role, // (新)
		RegisteredClaims: jwt.RegisteredClaims{ // 继承标准的JWT注册声明
			ExpiresAt: jwt.NewNumericDate(
				time.Now().Add(time.Duration(expire) * time.Second),
			), // 设置过期时间
			IssuedAt: jwt.NewNumericDate(time.Now()), // 设置签发时间
		},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) // 创建一个新的JWT token,使用HS256签名方法,并将claims作为负载
	return token.SignedString([]byte(secrect))                 // 使用提供的密钥对token进行签名,并返回生成的JWT字符串
}

三. 登录时生成带Role的Token

internal/service/user.go

func Login(req *dto.LoginRequest) (*model.User, string, error) {
	/// ... 原代码 ...
	
	// 传参生成JWT token(新: 传入记录Role值)
	token, err := jwtx.GenerateToken(user.Uid, user.Username, user.Role, config.GlobalConfig.JWT.Secret, config.GlobalConfig.JWT.Expire)
	if err != nil {
		return nil, "", errors.New("failed to generate token")
	}

	return user, token, nil
}

四. 解析Token

internal/middleware/auth.go

解析 Role 注入 Context,用于RBAC鉴权中间件进行校验

// JWTAuth 是一个Gin中间件函数,用于验证请求中的JWT token是否合法
func JWTAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		// ... 原代码 ...

		c.Set("uid", claims.Uid)           // 将解析后的用户ID存储在Gin的上下文中,供后续处理函数使用
		c.Set("username", claims.Username) // 将解析后的用户名存储在Gin的上下文中,供后续处理函数使用
		c.Set("role", claims.Role)         // (新) 将解析后的用户角色存储在Gin的上下文中,供后续处理函数使用

		c.Next() // 继续处理请求,调用下一个中间件或处理函数
	}
}

五. 编写RBAC中间件

internal/middleware/rbac.go

package middleware

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// RequireAdmin 是一个Gin中间件函数,用于验证当前登录用户是否具有管理员权限
// 如果用户不是admin,返回403Forbidden
func RequireAdmin() gin.HandlerFunc {
	return func(c *gin.Context) {
		role, exists := c.Get("role")
		if !exists || role != "admin" {
			c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
				"code":    403,
				"message": "admin permission required",
			})
			return
		}
		c.Next()
	}
}

六. 路由应用鉴权函数

我们为几个路由应用RequireAdmin()函数以实现鉴权

cmd/secgo-mall/main.go

// ... 原代码 ...
// 需要鉴权的私有路由
	protected := r.Group("/api")
	protected.Use(middleware.JWTAuth()) // 使用JWT鉴权中间件
	{
		// 在这里定义需要鉴权的路由
		protected.GET("/user/info", func(c *gin.Context) {
			uid, _ := c.Get("uid")
			username, _ := c.Get("username")
			role, _ := c.Get("role")
			response.Sucess(c, gin.H{
				"uid":      uid,
				"username": username,
				"role":     role, // (新)
			})
		})

		// 管理员创建商品
		protected.POST("/product/create", middleware.RequireAdmin(), handlers.CreateProduct)

		// 管理员创建秒杀活动
		protected.POST("/seckill/create", middleware.RequireAdmin(), handlers.CreateSeckill)
		
		// 管理员根据用户ID获取订单列表
		protected.GET("/order/list/:user_id", middleware.RequireAdmin(), handlers.GetOrderByUserID)
		
		// ... 其余路由 ...
	}
// ... 原代码 ...

七. 提权与测试

-- 将 uid=1 的用户设为管理员
UPDATE user SET role = 'admin' WHERE uid = 1;

-- 新注册用户默认 role='user'(GORM 自动处理)

我这里在给admin提权后,又注册了一个默认权限的guest用户

1) guest用户尝试查询某其他用户订单

RBAC_test_guest

2) admin用户查询某用户订单

RBAC_test_admin

posted @ 2026-04-17 17:46  Chuan81  阅读(19)  评论(0)    收藏  举报