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

ASP.NET Core:自定义AuthorizationHandler中Scoped服务注入问题

解决方案:ASP.NET Core多租户授权中CurrentTenantService实例复用问题

问题根源

单例的PermissionAuthorizationPolicyProvider如果直接注入Scoped服务(如ICurrentTenantService),会导致服务实例来自根容器而非当前请求域,因此无法复用已初始化租户数据的实例。同时,若权限策略构建逻辑过度依赖租户信息,也会加剧实例不匹配的问题。


修复步骤

1. 调整PermissionAuthorizationPolicyProvider实现,通过请求域获取Scoped服务

在单例的PolicyProvider中,不要直接注入Scoped服务,而是通过IHttpContextAccessor获取当前请求的RequestServices,以此解析请求域内的服务实例。

public class PermissionAuthorizationPolicyProvider : DefaultAuthorizationPolicyProvider
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly IOptions<AuthorizationOptions> _options;

    public PermissionAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options, IHttpContextAccessor httpContextAccessor)
        : base(options)
    {
        _options = options;
        _httpContextAccessor = httpContextAccessor;
    }

    public override async Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
    {
        // 优先使用默认策略
        var policy = await base.GetPolicyAsync(policyName);
        if (policy != null)
            return policy;

        // 从当前请求域获取Scoped服务
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null)
            return null;

        var tenantService = httpContext.RequestServices.GetRequiredService<ICurrentTenantService>();
        var securityService = httpContext.RequestServices.GetRequiredService<ISecurityService>();

        // 根据租户ID和策略名称获取权限要求
        var requiredPermissions = await securityService.GetPermissionsByPolicy(policyName, tenantService.TenantId);
        if (!requiredPermissions.Any())
            return null;

        // 构建自定义授权策略
        policy = new AuthorizationPolicyBuilder()
            .AddRequirements(new PermissionRequirement(requiredPermissions))
            .Build();

        // 按租户维度缓存策略(避免跨租户共享)
        _options.Value.AddPolicy($"{tenantService.TenantId}_{policyName}", policy);
        return policy;
    }
}

2. 确保服务注册顺序与生命周期正确

// 注册HttpContext访问器(必须)
services.AddHttpContextAccessor();

// 单例注册自定义PolicyProvider
services.AddSingleton<IAuthorizationPolicyProvider, PermissionAuthorizationPolicyProvider>();

// Scoped注册授权Handler(直接注入请求域服务)
services.AddScoped<IAuthorizationHandler, PermissionHandler>();

// 确保CurrentTenantService为Scoped生命周期
services.AddScoped<ICurrentTenantService, CurrentTenantService>();
services.AddScoped<ISecurityService, SecurityService>();

3. 优化PermissionHandler逻辑,直接使用请求域服务

由于Handler是Scoped生命周期,可直接注入ICurrentTenantService和ISecurityService,无需额外处理即可获取当前请求的实例:

public class PermissionHandler : AuthorizationHandler<PermissionRequirement>
{
    private readonly ICurrentTenantService _tenantService;
    private readonly ISecurityService _securityService;

    public PermissionHandler(ICurrentTenantService tenantService, ISecurityService securityService)
    {
        _tenantService = tenantService;
        _securityService = securityService;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
    {
        var userId = context.User.FindFirstValue(ClaimTypes.NameIdentifier);
        if (string.IsNullOrEmpty(userId))
        {
            context.Fail();
            return;
        }

        // 使用当前请求的租户实例校验权限
        var hasPermission = await _securityService.VerifyUserPermissions(userId, _tenantService.TenantId, requirement.RequiredPermissions);
        if (hasPermission)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

4. 保证租户中间件执行顺序

确保租户初始化中间件在授权中间件之前执行,保证进入授权流程时CurrentTenantService已加载租户数据:

app.UseMiddleware<TenantMiddleware>(); // 先初始化租户
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization(); // 再执行授权

额外注意事项

  • 不要在策略中缓存跨租户的权限规则,需按租户ID+策略名称作为缓存键,避免租户数据混淆。
  • 若必须将Handler注册为单例,需在HandleRequirementAsync中通过IServiceProvider创建请求域Scope来获取服务:
    using var scope = _serviceProvider.CreateScope();
    var tenantService = scope.ServiceProvider.GetRequiredService<ICurrentTenantService>();
    

内容的提问来源于stack exchange,提问作者Lam Trường

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:14:59