.NET API动态资源授权:如何注入请求资源参数实现权限验证
.NET API 基于角色与声明的动态权限实现方案
问题场景
系统定义了四种带权限继承性的角色:
- Admin User:可访问所有资源
- District User:可访问所属一个/多个学区内的所有建筑和学生
- Building User:可访问所属一个/多个建筑内的所有学生
- Student User:仅能访问自身声明关联的学生
当前已编写基础授权策略代码,但核心问题是:无法将请求中的动态资源参数(如示例里的学生ID)注入到授权逻辑中完成验证。
解决方案步骤
1. 调整权限需求类(移除硬编码参数)
请求参数是动态的,不能在注册策略时提前传入,因此修改需求类,不需要构造函数参数:
public class SingleStudentPolicyRequirement : IAuthorizationRequirement { // 无需硬编码参数,动态从请求上下文获取资源ID }
2. 改造授权处理器(动态提取请求参数并验证)
通过AuthorizationHandlerContext获取当前HttpContext,提取路由中的学生ID,再结合用户角色与声明完成权限校验:
public class StudentPolicyHandler : AuthorizationHandler<SingleStudentPolicyRequirement> { // 注入业务服务,用于查询学生所属的学区/建筑信息 private readonly IStudentService _studentService; public StudentPolicyHandler(IStudentService studentService) { _studentService = studentService; } protected override async Task HandleRequirementAsync( AuthorizationHandlerContext context, SingleStudentPolicyRequirement requirement) { // 获取当前请求上下文 if (context.Resource is not HttpContext httpContext) { context.Fail(); return; } // 从路由中解析学生ID if (!httpContext.Request.RouteValues.TryGetValue("id", out var idValue) || !int.TryParse(idValue.ToString(), out int studentId)) { context.Fail(); return; } // 1. Admin角色直接放行 if (context.User.IsInRole("Admin User")) { context.Succeed(requirement); return; } // 2. Student User:验证用户声明中的学生ID是否匹配 if (context.User.IsInRole("Student User")) { var studentIdClaim = context.User.FindFirst("StudentId"); if (studentIdClaim != null && int.Parse(studentIdClaim.Value) == studentId) { context.Succeed(requirement); return; } } // 3. 查询目标学生的所属学区/建筑信息 var student = await _studentService.GetByIdAsync(studentId); if (student == null) { context.Fail(); return; } // 4. District User:验证用户是否拥有该学生所属学区的权限声明 var userDistrictIds = context.User.Claims .Where(c => c.Type == "DistrictId") .Select(c => int.Parse(c.Value)) .ToList(); if (userDistrictIds.Contains(student.DistrictId)) { context.Succeed(requirement); return; } // 5. Building User:验证用户是否拥有该学生所属建筑的权限声明 var userBuildingIds = context.User.Claims .Where(c => c.Type == "BuildingId") .Select(c => int.Parse(c.Value)) .ToList(); if (userBuildingIds.Contains(student.BuildingId)) { context.Succeed(requirement); return; } // 所有条件不满足,验证失败 context.Fail(); await Task.CompletedTask; } }
3. 注册授权策略与处理器
在Startup.cs或Program.cs中注册策略,无需传入固定参数;同时因为处理器依赖业务服务,使用AddScoped而非Singleton:
// 配置授权策略 options.AddPolicy("StudentPolicy", policy => { policy.Requirements.Add(new SingleStudentPolicyRequirement()); }); // 注册授权处理器 services.AddScoped<IAuthorizationHandler, StudentPolicyHandler>();
4. 控制器代码保持不变
原控制器标记无需修改,依然使用策略注解:
// GET: api/v1/Student/5 [Authorize(Policy="StudentPolicy")] [HttpGet("{id}")] public async Task<ActionResult<StudentDto>> GetStudent(int id) { var _student = await _studentService.GetByIdAsync(id); return Ok(_student); }
内容的提问来源于stack exchange,提问作者dgo
相关产品推荐
相关产品推荐

