未调用ASP.NET Core AuthorizationHandler

时间:2020-04-17 22:34:21

标签: c# asp.net-core

我正在尝试添加一些基于authorisation的自定义角色,但是我无法 将启动配置为调用我的AuthorizationHandler

我在GitHub上找到了一些相关信息:here。 这是错误吗?

我正在使用ASP.NET Core 3.1,初始化如下:

1::这将使用Dapper ORM从数据库中检索网址/角色:

private List<UrlRole> GetRolesRoutes()
{
    var urlRole = DapperORM.ReturnList<UrlRole>("user_url_role_all");
    return urlRole.Result.ToList();
}

2 :在我的启动程序中,我获得了url /角色并将结果存储在全局变量中:

public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
    this.environment = env;
    UrlRoles = GetRolesRoutes();
}

3:我的配置是:注意一起传递的UrlRoles

 public void ConfigureServices(IServiceCollection services)
 {
     // .. snip   
     services.AddAuthorization(o =>
     o.AddPolicy(_RequireAuthenticatedUserPolicy,
            builder => builder.RequireAuthenticatedUser()));

     services.AddAuthorization(options =>
     {
         options.AddPolicy("Roles", policy =>
         policy.Requirements.Add(new UrlRolesRequirement(UrlRoles)));
     });


    services.AddSingleton<AuthorizationHandler<UrlRolesRequirement>, PermissionHandler>();
}

5:我的处理程序:未被调用

public class PermissionHandler : AuthorizationHandler<UrlRolesRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UrlRolesRequirement urlRolesRequirement)
    {
        var pendingRequirements = context.PendingRequirements.ToList();
        foreach (var requirement in pendingRequirements)
        {
        }
        return Task.CompletedTask;
    }
}

6:我的要求班级:

public class UrlRolesRequirement : IAuthorizationRequirement
{
    private List<UrlRole> UrlRoles { get; }

    public UrlRolesRequirement(List<UrlRole> urlRoles)
    {
        UrlRoles = urlRoles;
    }      
}

当我调试ASP.NET Core AuthorizationHandler时,我再也没有看到自定义需求是我在启动中配置的需求。我希望看到需求,如果存在需求,那么我会假设发生“回调”。但是由于某种原因,我的配置无法添加要求。

public virtual async Task HandleAsync(AuthorizationHandlerContext context)
{
    if (context.Resource is TResource)
    {
        foreach (var req in context.Requirements.OfType<TRequirement>())
        {
            await HandleRequirementAsync(context, req, (TResource)context.Resource);
        }
    }
}

2 个答案:

答案 0 :(得分:1)

在不告知ASP.NET Core这样做的情况下,它将不会使用您配置的策略对任何内容进行授权。授权策略使您可以预定义复杂的授权条件,以便可以在需要时重用此行为。但是,默认情况下它并不适用,并且无法考虑您已经配置了两个策略:应采用哪些策略?他们全部?那么为什么要配置单独的策略?

因此,除非您明确告知框架,否则不会使用任何策略来授权用户。一种常见的方法是将[Authorize]属性与策略名称一起使用。您可以将其置于控制器操作上,也可以置于控制器本身上,以使其所有操作均受此策略授权:

[Authorize("Roles")] // ← this is the policy name
public class ExampleController : Controller
{
    // …
}

如果您有一个要在大多数情况下使用 来授权用户的策略,则可以将此策略配置为默认策略:

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .Build();
}

例如,这将定义一个策略,该策略要求经过身份验证的用户为默认用户。因此,只要您使用[Authorize]属性而没有来指定明确的策略,那么它将使用那个默认策略。

这仍然需要您以某种方式标记您需要授权的路线。除了使用[Authorize]属性之外,您还可以在更中央的位置进行此操作:app.UseEndpoints()类中的Startup调用。

endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}")
    .RequireAuthorization("Roles");

这是控制器的默认路由模板,但会调用RequireAuthorization,这基本上要求在与该路由模板匹配的所有路由上使用Roles授权策略。

您还可以在此位置使用此路由为不同的路由配置不同的默认授权策略:通过拆分路由模板,您可以使用不同的路由模板对MapControllerRoute进行多次调用,这些路由模板都指定了自己的授权策略

我当时在想,不是装饰每个控制器或每个动作,而是想在数据库中有一些预配置映射,然后在管道中验证用户角色或在用户认证时分配的角色。然后,当用户尝试访问url时,将验证用户角色,并授予或拒绝访问。

可以将逻辑如何精确地授权用户到验证您需求的授权处理程序中。您仍然可以对所有要测试的路由启用具有此要求的策略。

但是,我通常不建议这样做:授权要求本来就很简单,通常,您希望能够 在不访问数据库或其他外部资源的情况下进行验证。您想直接使用用户的声明快速做出是否授权用户访问某些内容的决定。毕竟,这些检查会针对每个请求运行,因此您希望快速进行检查。基于声明的授权的一个主要好处是您不需要在每个请求上都访问数据库,因此您应通过确保在其声明中可以使用授权用户的所有内容来保持这一优势。

答案 1 :(得分:0)

这是一个经过测试的解决方案,可以启用runtime configuration changes。 还减轻了装饰每个班级或每个动作的负担。

Startup中添加Role Authorization Requirement,然后注册 RoleService,将负责确保授权特定角色访问特定URL。

这是我们在其中配置需求以及角色服务的Startup.cs:

services.AddAuthorization(options =>
{
    options.DefaultPolicy = new AuthorizationPolicyBuilder()
        .RequireAuthenticatedUser()
        .AddRequirements(new UrlRolesRequirement())
        .Build();
});

services.AddSingleton<IUserService, UserService>(); // authenticate
services.AddSingleton<IUserRoleService, UserRoleService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, PermissionHandler>(); // authorise

角色IUserRoleService:-UserRoleService实现 通过在高速缓存的映射中查找或从数据库中检索数据,根据由允许的url /角色条目组成的配置映射来验证用户声明的角色(JWT声明)。

典型的url(path) to role地图具有以下格式,从数据库中检索,然后进行缓存(如果查找失败,则从数据库):

/path/to/resource        ROLE
public interface IUserRoleService
{
    public bool UserHasAccess(ClaimsPrincipal user, string path);
}

权限处理程序:

public class PermissionHandler : IAuthorizationHandler
{
    private readonly IUserRoleService userRoleService;
    private readonly IHttpContextAccessor contextAccessor;

    public PermissionHandler(IUserRoleService userRoleService, IHttpContextAccessor contextAccessor)
    {
        this.userRoleService = userRoleService;
        this.contextAccessor = contextAccessor;
    }

    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();
        foreach (var requirement in pendingRequirements)
        {
            if (!(requirement is UrlRolesRequirement)) continue;

            var httpContext = contextAccessor.HttpContext;
            var path = httpContext.Request.Path;
            if (userRoleService.UserHasAccess(context.User, path))
            {
                context.Succeed(requirement);
                break;
            }
        }
        return Task.CompletedTask;
    }
}

角色要求-只是POCO

public class UrlRolesRequirement : IAuthorizationRequirement
{           
}

这是UserRoleService的部分实现,用于验证声明的JWT角色。

private bool ValidateUser(ClaimsPrincipal user, string path)
{
    foreach (var userClaim in user.Claims)
    {
        if (!userClaim.Type.Contains("claims/role")) continue;

        var role = userClaim.Value;
        var key = role + SEPARATOR + path;

        if (urlRoles.ContainsKey(key))
        {
            var entry = urlRoles[key];
            if (entry.Url.Equals(path) && entry.Role.Equals(role))
            {
                return true;
            }
        }

    }
    Console.WriteLine("Access denied: " + path);
    return false;
}