You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 08:45:06