具有多个注册的 IAuthorizationHandler - 依赖项解析器如何选择正确的实现?

ati*_*yar 9 dependency-injection dependency-resolver asp.net-core

考虑类的ConfigureServices方法中的以下代码Startup-

services.AddTransient<IAuthorizationHandler, BlockUsersHandler>();
services.AddTransient<IAuthorizationHandler, BlockClaimHandler>();

services.AddAuthorization(option =>
{                
    option.AddPolicy("NotHacker", policy =>
    {
        policy.AddRequirements(new BlockUsersRequirement("Hacker"));
    });                
    option.AddPolicy("NotThatClaim", policy =>
    {
        policy.AddRequirements(new BlockClaimRequirement(new Claim("ThatClaimType", "ThatClaim")));
    });
});
Run Code Online (Sandbox Code Playgroud)

这些是自定义类实现 -

public class BlockUsersRequirement : IAuthorizationRequirement
{
    // Code goes here
}

public class BlockUsersHandler : AuthorizationHandler<BlockUsersRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockUsersRequirement requirement)
    {
        // Code goes here
    }
}

public class BlockClaimRequirement : IAuthorizationRequirement
{
    // Code goes here
}

public class BlockClaimHandler : AuthorizationHandler<BlockClaimRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BlockClaimRequirement requirement)
    {            
        // Code goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

我的理解是,每当遇到对服务的依赖时,内置的依赖解析器都会提供为该服务注册的具体实现,如果我注册了一个服务的多个实现,那么最后一次注册将生效。

在上面的代码中,注册了两个实现IAuthorizationHandler并且两个授权策略都可以正常工作。

那么,依赖解析器如何决定何时选择哪个实现呢?并基于什么?

编辑- 2019年7月28日
因此,作为@马丁下面回答,貌似依赖解析器可以推断从执行IAuthorizationRequirementAuthorizationHandler<TRequirement>,从该处理程序实现的派生。

但是你实际上可以通过直接实现IAuthorizationHandler接口而不用派生来创建一个 Handler 类AuthorizationHandler<TRequirement>-

public class DeletePermissionRequirement : IAuthorizationRequirement
{
    // Nothing here
}

public class DeletePermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        // Code goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

所以,现在IAuthorizationRequirement在 Handler 的签名中没有可以推断的。

此外,您可以为单个需求添加多个 Handler 实现 -

public class BuildingEntryRequirement : IAuthorizationRequirement
{
    // Nothing here
}

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        // Code goes here
    }
}

public class TemporaryPassHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        // Code goes here
    }
}
Run Code Online (Sandbox Code Playgroud)

考虑到这些新的实现,ConfigureServices方法中的代码看起来像 -

services.AddTransient<IAuthorizationHandler, BlockUsersHandler>();
services.AddTransient<IAuthorizationHandler, BlockClaimHandler>();
services.AddTransient<IAuthorizationHandler, DeletePermissionHandler>();
services.AddTransient<IAuthorizationHandler, BadgeEntryHandler>();
services.AddTransient<IAuthorizationHandler, TemporaryPassHandler>();

services.AddAuthorization(option =>
{                
    option.AddPolicy("NotHacker", policy =>
    {
        policy.AddRequirements(new BlockUsersRequirement("Hacker"));
    });                
    option.AddPolicy("NotThatClaim", policy =>
    {
        policy.AddRequirements(new BlockClaimRequirement(new Claim("ThatClaimType", "ThatClaim")));
    });
    option.AddPolicy("CanDelete", policy =>
    {
        policy.AddRequirements(new DeletePermissionRequirement());
    });
    option.AddPolicy("BadgeEntry", policy =>
    {
        policy.AddRequirements(new BuildingEntryRequirement());
    });
});
Run Code Online (Sandbox Code Playgroud)

当然,所有授权策略都运行良好。

那么,依赖解析器如何选择正确的实现呢?

Mar*_*cik 6

它使用需求的类型来决定使用哪个处理程序。

可以有更多的同时授权处理程序,其需求类型不同。

可以在一项政策中检查更多要求。

此外,当调用授权服务时,它会选择正确的处理程序:

IAuthorizationService _authorizationService; // injected

_authorizationService.AuthorizeAsync(User, resourceId, new MyAuthorizationRequirement(UserAccessScope.Account, resourceId, requiredRole));
Run Code Online (Sandbox Code Playgroud)

更新

默认行为是

  • 对于给定的AuthorizationHandlerContext所有已注册IAuthorizationHandler处理程序都会进行评估
  • 对于每个处理程序,HandleAsync都会调用该方法
  • 从抽象类派生的处理程序以这种方式AuthorizationHandler<TRequirement>实现HandleAsync该方法:
public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
    foreach (var req in context.Requirements.OfType<TRequirement>())
    {
        await HandleRequirementAsync(context, req);
    }
}
Run Code Online (Sandbox Code Playgroud)

这意味着处理程序按类型过滤需求。它还意味着不是从抽象类派生的处理程序AuthorizationHandler<TRequirement>有自己的HandleAsync方法实现。

AuthorizationHandler<TRequirement>从抽象类继承的授权处理程序与不从抽象类继承的授权处理程序之间的区别归结为HandleAsync接口方法的IAuthorizationHandler实现方式。AuthorizationHandler<TRequirement>具有上述默认实现,通用接口的实现者IAuthorizationHandler需要提供自己的实现。

关于单个需求的多个处理程序实现的第二部分的答案是,将评估具有给定类型需求的所有处理程序,并且如果其中任何一个成功并且没有一个显式失败(已在上下文中调用方法Fail) ) 那么该操作将被授权。