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

.NET Core中HasPermissionAttribute注入服务始终为Null的解决方法

问题根源分析

你遇到的核心问题在于特性(Attribute)的生命周期与依赖注入(DI)不兼容:

  • 特性是编译阶段就被实例化的标记类,并非由DI容器管理,所以你注册的AddScoped<HasPermissionAttribute>不会被框架使用,框架会直接调用特性的构造函数创建实例。
  • 你带RolesKey参数的构造函数中,_serviceScopeFactory字段从未被赋值(这个字段仅在另一个带DI参数的构造函数中初始化),自然会出现Null引用。
  • 即使强行在特性构造函数中尝试获取服务,此时DI容器可能还未完全初始化,也无法正确获取服务实例。
正确解决方案:ASP.NET Core授权规范实现

ASP.NET Core的授权设计中,AuthorizeAttribute仅用于标记授权策略,实际的权限验证逻辑应该放在AuthorizationHandler中(Handler由DI管理,可正常注入服务)。以下是完整重构方案:

1. 重构HasPermissionAttribute

让它仅作为标记类,存储需要验证的权限/模块/角色参数,绑定统一的授权策略:

public sealed class HasPermissionAttribute : AuthorizeAttribute
{
    // 存储不同类型的验证参数
    public PermissionKeys[] Permissions { get; }
    public SystemModule[] Modules { get; }
    public RolesKey[] Roles { get; }

    // 直接指定权限的构造函数
    public HasPermissionAttribute(params PermissionKeys[] permissions)
    {
        Policy = "PermissionValidationPolicy";
        Permissions = permissions;
    }

    // 指定模块的构造函数
    public HasPermissionAttribute(params SystemModule[] modules)
    {
        Policy = "PermissionValidationPolicy";
        Modules = modules;
    }

    // 指定角色的构造函数
    public HasPermissionAttribute(params RolesKey[] roles)
    {
        Policy = "PermissionValidationPolicy";
        Roles = roles;
    }
}

2. 创建授权需求类(遵循规范)

作为授权逻辑的载体,也可以省略,但建议保留以符合ASP.NET Core授权模式:

public class PermissionRequirement : IAuthorizationRequirement
{
    // 这里可以添加通用参数,或者直接在Handler中读取特性参数
}

3. 实现权限验证Handler

这是核心逻辑所在,由DI管理,可正常注入IPermissionService:

public class PermissionValidationHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly IPermissionService _permissionService;

    // 构造注入权限服务
    public PermissionValidationHandler(IPermissionService permissionService)
    {
        _permissionService = permissionService;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        // 获取当前请求的端点,从中提取HasPermissionAttribute
        if (context.Resource is not HttpContext httpContext || httpContext.GetEndpoint() is not Endpoint endpoint)
        {
            context.Fail();
            return;
        }

        var permissionAttribute = endpoint.Metadata.GetMetadata<HasPermissionAttribute>();
        if (permissionAttribute == null)
        {
            context.Fail();
            return;
        }

        // 获取当前用户ID(根据你的认证逻辑调整Claim类型)
        var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            context.Fail();
            return;
        }

        bool hasValidPermission = false;

        // 处理直接指定权限的场景
        if (permissionAttribute.Permissions?.Any() == true)
        {
            var requiredPermissionTitles = permissionAttribute.Permissions
                .Select(p => p.GetAttributeOfType<DescribePermissionAttribute>().Title);
            hasValidPermission = await _permissionService.UserHasPermissionsAsync(userId, requiredPermissionTitles);
        }
        // 处理指定模块的场景
        else if (permissionAttribute.Modules?.Any() == true)
        {
            var modulePermissionTitles = permissionAttribute.Modules.SelectMany(module =>
                typeof(PermissionKeys).GetFields()
                    .Where(f => f.GetCustomAttribute<DescribePermissionAttribute>()?.Module == module)
                    .Select(f => f.GetValue(null)).Cast<PermissionKeys>()
                    .Select(p => p.GetAttributeOfType<DescribePermissionAttribute>().Title)
            );
            hasValidPermission = await _permissionService.UserHasPermissionsAsync(userId, modulePermissionTitles);
        }
        // 处理指定角色的场景
        else if (permissionAttribute.Roles?.Any() == true)
        {
            var rolePermissionTitles = await _permissionService.GetRolePermissions(permissionAttribute.Roles);
            hasValidPermission = await _permissionService.UserHasPermissionsAsync(userId, rolePermissionTitles);
        }

        if (hasValidPermission)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

4. 注册服务与授权策略

在Program.cs或Startup.cs中完成DI注册:

// 注册权限服务
builder.Services.AddScoped<IPermissionService, PermissionService>();
// 注册授权Handler
builder.Services.AddScoped<PermissionValidationHandler>();

// 配置授权策略
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("PermissionValidationPolicy", policy =>
    {
        policy.Requirements.Add(new PermissionRequirement());
    });
});

5. 使用方式不变

你依然可以像之前一样在接口/方法上标记特性:

[HasPermission(RolesKey.Admin)]
public IActionResult AdminOnly()
{
    return Ok();
}

[HasPermission(SystemModule.OrderManagement)]
public IActionResult OrderOperations()
{
    return Ok();
}

内容的提问来源于stack exchange,提问作者abdelrhman tarekk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 10:35:37