rbac
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
// Package middleware 的权限授权部分:定义权限提供接口、权限加载中间件与接口守卫。
|
||||
// 设计为“认证后授权”:PermissionLoader 在 AdminAuth 之后运行,把管理员角色与权限集
|
||||
// 注入 gin.Context;RequirePerm / RequireRole 在路由层比对集合,缺失即返回 403。
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"portal-mini-go/internal/model/admin"
|
||||
"portal-mini-go/internal/pkg/errors"
|
||||
)
|
||||
|
||||
// PermissionProvider 是权限数据的提供方(由 service/admin/rbac 实现)。
|
||||
// 中间件仅依赖此接口,便于单测以 mock 替换。
|
||||
type PermissionProvider interface {
|
||||
// Load 返回管理员的角色 code 与权限 code 集合(优先走缓存)。
|
||||
Load(ctx context.Context, adminID uint64) (roles, perms []string, err error)
|
||||
RolesOf(ctx context.Context, adminID uint64) ([]string, error)
|
||||
PermissionsOf(ctx context.Context, adminID uint64) ([]string, error)
|
||||
// MenuTree 返回该管理员可见的菜单树(按权限过滤,超级管理员返回完整树)。
|
||||
MenuTree(ctx context.Context, adminID uint64) ([]admin.MenuNode, error)
|
||||
}
|
||||
|
||||
// 后台权限 code 常量(接口级),供路由守卫引用,避免散落字符串。
|
||||
const (
|
||||
PermMiniView = "mini:view"
|
||||
PermMiniManage = "mini:manage"
|
||||
PermUserView = "user:view"
|
||||
PermUserManage = "user:manage"
|
||||
PermAdminManage = "admin:manage"
|
||||
PermRoleManage = "role:manage"
|
||||
PermPermManage = "perm:manage"
|
||||
)
|
||||
|
||||
const (
|
||||
adminRolesCtxKey = "admin_roles"
|
||||
adminPermsCtxKey = "admin_perms"
|
||||
)
|
||||
|
||||
// PermissionLoader 在 AdminAuth 之后加载管理员权限集并写入 context。
|
||||
// 加载失败时记告警日志并放行(fail-closed 由守卫层保证:空集必然不通过)。
|
||||
func PermissionLoader(p PermissionProvider) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
id, ok := AdminIDUint(c)
|
||||
if !ok {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
roles, perms, err := p.Load(c.Request.Context(), id)
|
||||
if err != nil {
|
||||
slog.Warn("load admin permissions failed",
|
||||
slog.Uint64("admin_id", id), slog.Any("err", err))
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.Set(adminRolesCtxKey, roles)
|
||||
set := make(map[string]struct{}, len(perms))
|
||||
for _, pc := range perms {
|
||||
set[pc] = struct{}{}
|
||||
}
|
||||
c.Set(adminPermsCtxKey, set)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequirePerm 接口守卫:管理员需同时拥有全部给定权限(超级管理员短路放行)。
|
||||
func RequirePerm(perms ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if HasRole(c, admin.SuperAdminRoleCode) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
got := permsOf(c)
|
||||
for _, need := range perms {
|
||||
if _, ok := got[need]; !ok {
|
||||
c.Error(errors.ErrForbidden)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// RequireRole 角色守卫:管理员需拥有任一给定角色(超级管理员始终通过)。
|
||||
func RequireRole(roles ...string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if HasRole(c, admin.SuperAdminRoleCode) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
for _, need := range roles {
|
||||
if HasRole(c, need) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Error(errors.ErrForbidden)
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
// HasRole 判断 context 中是否含指定角色。
|
||||
func HasRole(c *gin.Context, code string) bool {
|
||||
if v, ok := c.Get(adminRolesCtxKey); ok {
|
||||
if roles, ok := v.([]string); ok {
|
||||
for _, r := range roles {
|
||||
if r == code {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasPerm 判断 context 中是否含指定权限。
|
||||
func HasPerm(c *gin.Context, code string) bool {
|
||||
_, ok := permsOf(c)[code]
|
||||
return ok
|
||||
}
|
||||
|
||||
func permsOf(c *gin.Context) map[string]struct{} {
|
||||
if v, ok := c.Get(adminPermsCtxKey); ok {
|
||||
if set, ok := v.(map[string]struct{}); ok {
|
||||
return set
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user