134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
// 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
|
||
}
|