角色权限控制需按场景选模型:ASP.NET Core Identity 用细粒度 Claim 和策略注册;WinForms/WPF 用接口抽象权限服务;RBAC 避免字符串存权限、需有效期和显式继承;JWT 权限变更靠 Redis 黑名单或 reference token 实时生效。

角色权限控制不是加个 Role 字段就能跑起来的,核心在于「谁在什么上下文里能做什么」——C# 里没有银弹,得按实际场景选模型,否则后期改起来比重写还疼。
基于 ASP.NET Core Identity 的声明式权限(适合 Web API / MVC)
这是最贴近 .NET 生态的方案,但容易误以为 AddIdentity 一配就完事。实际上关键在声明(Claim)粒度和策略注册时机。
-
ClaimsPrincipal是运行时权限判断的唯一依据,所有[Authorize(Roles = "Admin")]或context.User.IsInRole("Editor")都依赖它,别绕过它自己查数据库 - 权限不应只靠
Role声明,建议用细粒度Claim:比如new Claim("Permission", "Order:Delete"),避免角色爆炸(如 “超级编辑”“临时审核员”) - 自定义策略必须在
Program.cs里注册,且RequireClaim/RequireAssertion要早于UseAuthentication(),否则中间件不生效 - 示例:检查用户是否拥有某资源的操作权,不能只看角色,得结合
Resource参数做IAuthorizationHandler实现
基于策略模式的手动权限校验(适合 WinForms / WPF / 后台服务)
没有 HTTP 上下文或 Identity 中间件时,硬编码 if (user.Role == "Admin") 是最常见也最危险的做法——权限逻辑散落在各处,改一个功能要翻十处代码。
- 把权限判断抽成接口,如
IPermissionService.CanAccess(string resource, string operation),实现类可对接数据库、配置文件甚至远程鉴权服务 - 避免用字符串硬编码权限名,用
public static class Permissions { public const string User_Read = "User:Read"; }统一管理 - WinForms 中控件级隐藏/禁用,不要在每个
Button_Click里重复判断,而应在Form.Load或 ViewModel 初始化时批量绑定Enabled状态 - 注意线程安全:后台服务中若权限数据缓存在内存(如
ConcurrentDictionary),更新权限后需触发清除或版本号刷新,否则脏读导致越权
RBAC 模型落地时最容易崩的三个点
RBAC(基于角色的访问控制)听着规范,但 C# 项目里常因设计偏差直接退化成“伪 RBAC”。
-
Role表和Permission表之间必须是多对多,别用逗号分隔的字符串字段存权限列表——没法索引、无法原子增删、SQL 查询反人类 - 用户-角色关系不能只存一张表,得有有效期字段(
ValidFrom/ValidTo)和启用状态(IsActive),否则离职人员权限无法及时冻结 - 权限继承要谨慎:A 角色继承 B 角色 ≠ A 自动获得 B 的所有权限,而是应走显式授权链(如
RoleInheritance关联表),否则审计时根本说不清权限来源 - 别在 Entity Framework 的
OnModelCreating里用HasIndex给RoleId和PermissionId单独建索引——联合索引HasIndex(r => new { r.RoleId, r.PermissionId })才能加速查询
权限变更后如何实时生效(而非等 Token 过期)
JWT Token 一旦签发,权限就固化了。用户改了角色或权限,前端还在用旧 Token,后端不做干预就会持续越权或拒访。
- 短期方案:Token 加入
jti(唯一 ID)+ 后端维护一个 Redis 黑名单,每次鉴权前先查redis.Exists("jti:{token_jti}") - 长期方案:改用 reference token(引用令牌),每次请求都调用
IntrospectionEndpoint实时验证,适合高安全要求系统(如金融后台) - 客户端主动刷新不可靠:别指望前端监听到“权限变更”就自动登出重登,应由后端在关键操作(如
UpdateUserRoles)后主动使对应用户的全部 Token 失效(通过清空其UserId相关的 Redis key 前缀) - 调试时注意:ASP.NET Core 默认 JWT 不校验
nbf(Not Before)和exp外的字段,若自定义了CheckSignature或ValidateAudience,得手动开启ValidateIssuerSigningKey = true,否则权限篡改无感知
权限不是加几个 if 就完事的事,真正难的是边界——比如「同一用户在不同租户下权限不同」「某个按钮既要校验权限又要校验数据归属」,这些地方没抽象好,后面加个新租户就得改半个项目。


















