基于外部API动态指令的模块权限缓存控制实现方案咨询
动态规则驱动的模块权限控制实现方案
我完全懂你的痛点——ViRuSTriNiTy那套静态权限模型根本扛不住外部API返回的动态规则型指令,这些规则跟着用户关联权限实时变化,还得缓存起来做请求校验。下面是一套能落地的分步方案,精准贴合你的需求:
第一步:事件触发+动态规则缓存
先搞定AuthorizationServiceEventHandler的事件监听,在Checking/Complete事件触发时拉取外部API的规则,然后和用户绑定缓存起来。缓存key要精准到用户,比如用user:{userId}:dynamic-permission-rules,避免串权。
给你个代码示例(可根据自身技术栈调整):
public class AuthorizationServiceEventHandler : IEventHandler<AuthorizationCheckingEvent>, IEventHandler<AuthorizationCompleteEvent> { private readonly IExternalPermissionApiClient _externalApiClient; private readonly ICacheManager _cacheManager; public AuthorizationServiceEventHandler(IExternalPermissionApiClient externalApiClient, ICacheManager cacheManager) { _externalApiClient = externalApiClient; _cacheManager = cacheManager; } public async Task HandleEventAsync(AuthorizationCheckingEvent eventData) { await FetchAndCacheRulesForUser(eventData.UserId); } public async Task HandleEventAsync(AuthorizationCompleteEvent eventData) { await FetchAndCacheRulesForUser(eventData.UserId); } private async Task FetchAndCacheRulesForUser(Guid userId) { // 调用外部API获取规则(修正笔误:实际为hospital相关规则) var dynamicRules = await _externalApiClient.GetUserPermissionRulesAsync(userId); // 设置缓存,添加过期时间(比如15分钟,可根据业务更新频率调整) var cacheKey = $"user:{userId}:dynamic-permission-rules"; await _cacheManager.SetAsync(cacheKey, dynamicRules, TimeSpan.FromMinutes(15)); } }
第二步:规则解析+请求时权限校验
因为外部API返回的是规则性指令(比如"无房产用户禁止访问hospital模块"),不是直接指定模块,所以得做一层规则解析,把文字规则转成可执行的校验逻辑。
1. 定义规则模型
用来接收外部API返回的数据结构:
public class DynamicPermissionRule { public string ModuleName { get; set; } // 目标模块名,比如"hospital" public string Condition { get; set; } // 规则条件,比如"userHasNoHouse" public string UnauthorizedMsg { get; set; } // 未授权提示,比如"You have no house" }
2. 实现权限校验过滤器
在控制器请求进来时自动触发校验,这里用异步Action过滤器举例:
public class DynamicPermissionFilter : IAsyncActionFilter { private readonly ICacheManager _cacheManager; private readonly IUserContext _userContext; public DynamicPermissionFilter(ICacheManager cacheManager, IUserContext userContext) { _cacheManager = cacheManager; _userContext = userContext; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var userId = _userContext.CurrentUserId; var cacheKey = $"user:{userId}:dynamic-permission-rules"; var userRules = await _cacheManager.GetAsync<List<DynamicPermissionRule>>(cacheKey); // 缓存失效兜底:重新拉取规则并更新缓存 if (userRules == null) { var apiClient = context.HttpContext.RequestServices.GetService<IExternalPermissionApiClient>(); userRules = await apiClient.GetUserPermissionRulesAsync(userId); await _cacheManager.SetAsync(cacheKey, userRules, TimeSpan.FromMinutes(15)); } // 从请求中获取当前访问的模块(可从控制器特性、路由参数提取) var targetModule = GetTargetModule(context); // 匹配当前模块的规则 var matchedRule = userRules.FirstOrDefault(r => r.ModuleName.Equals(targetModule, StringComparison.OrdinalIgnoreCase)); if (matchedRule != null) { // 执行规则校验,判断用户是否触发禁止条件 if (await IsRuleTriggered(matchedRule.Condition, userId, context)) { // 返回401状态码及指定提示信息 context.Result = new ObjectResult(new { Message = matchedRule.UnauthorizedMsg }) { StatusCode = StatusCodes.Status401Unauthorized }; return; } } // 校验通过,继续执行请求 await next(); } // 从控制器/路由中提取模块名的逻辑 private string GetTargetModule(ActionExecutingContext context) { // 示例:用自定义特性标记控制器所属模块 var moduleAttr = context.Controller.GetType().GetCustomAttribute<ModuleAttribute>(); return moduleAttr?.ModuleName ?? context.RouteData.Values["module"]?.ToString(); } // 规则条件校验逻辑 private async Task<bool> IsRuleTriggered(string condition, Guid userId, ActionExecutingContext context) { var userProfileService = context.HttpContext.RequestServices.GetService<IUserProfileService>(); // 根据外部API返回的condition值做对应校验 switch (condition) { case "userHasNoHouse": // 校验用户是否无房产,是则触发禁止规则 return !await userProfileService.UserHasHouseAsync(userId); // 可扩展其他规则条件,比如"userHasNoHospital"等 default: // 默认不触发禁止,允许访问 return false; } } }
3. 给控制器绑定过滤器
给需要权限校验的控制器加上标记:
// 自定义特性标记模块名 [AttributeUsage(AttributeTargets.Class)] public class ModuleAttribute : Attribute { public string ModuleName { get; set; } public ModuleAttribute(string moduleName) => ModuleName = moduleName; } // 给hospital控制器添加权限校验 [Module("hospital")] [ServiceFilter(typeof(DynamicPermissionFilter))] public class HospitalController : ControllerBase { // 你的接口方法实现 }
第三步:进阶优化——用规则引擎替代硬编码
如果规则越来越复杂,硬写switch-case会很难维护,可以用表达式树或者轻量级规则引擎(比如NRules、FluentValidation)来解析规则。比如让外部API返回可解析的表达式字符串,比如"User.HouseCount == 0",然后用动态表达式编译执行:
private async Task<bool> IsRuleTriggered(string conditionExpr, Guid userId, ActionExecutingContext context) { var userProfile = await context.HttpContext.RequestServices .GetService<IUserProfileService>() .GetUserProfileAsync(userId); // 用动态表达式解析器编译执行规则 var param = Expression.Parameter(typeof(UserProfile), "User"); var lambda = System.Linq.Dynamic.Core.DynamicExpressionParser.ParseLambda( new[] { param }, typeof(bool), conditionExpr); var compiledFunc = (Func<UserProfile, bool>)lambda.Compile(); // 返回规则是否触发(true表示触发禁止条件) return compiledFunc(userProfile); }
这样外部API的规则可以更灵活,比如"User.Roles.Contains('Admin')"、"User.HospitalCount > 0"等,不用改代码就能支持新规则。
第四步:缓存与异常兜底优化
- 缓存击穿处理:用分布式锁(比如Redis的SETNX锁)避免缓存失效时大量请求同时打外部API,减少API压力。
- 主动缓存更新:如果外部API支持webhook,当规则变更时让API主动通知你的服务,清空对应用户的缓存,保证规则实时生效。
- API降级:当外部API不可用时,返回默认权限策略(比如禁止所有非基础访问),避免服务雪崩。
- 日志记录:把规则校验的过程和结果记下来,方便排查权限问题。
内容的提问来源于stack exchange,提问作者buff
相关产品推荐
相关产品推荐

