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用户尝试查询某其他用户订单

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


浙公网安备 33010602011771号