ASP.NET Core 3.0中的"context.Resource as AuthorizationFilterContext"返回null。

12

我正在尝试按照教程实现自定义授权需求。看起来,context.Resource不再包含AuthorizationFilterContext,因此:

var authFilterContext = context.Resource as AuthorizationFilterContext;

返回null,导致其余逻辑失败。由于为null,我也无法获取查询字符串值。 以下是代码:

public class CanEditOnlyOtherAdminRolesAndClaimsHandler :
   AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
    {
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
       ManageAdminRolesAndClaimsRequirement requirement)
        {
            var authFilterContext = context.Resource as AuthorizationFilterContext;
            if (authFilterContext == null)
            {
                return Task.CompletedTask;
            }

            string loggedInAdminId =
                context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;

            string adminIdBeingEdited = authFilterContext.HttpContext.Request.Query["userId"];

            if (context.User.IsInRole("Admin") &&
                context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
                adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

我该如何在ASP.NET Core 3.0中解决这个问题?


你读过这个吗?它讲的是基于资源的授权:https://learn.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-3.1#write-a-resource-based-handler - Matt U
我不认为那个页面能解决我的问题。你能详细说明一下吗? - Giorgi Aptsiauri
实际上,我可能误解了你的问题。抱歉。这个类似问题的答案可能是你需要的:https://dev59.com/iFkS5IYBdhLWcg3wp4Mi#57488678 这个答案特别适用于 .Net Core 3.0。 - Matt U
6个回答

12
这是由于.NET Core 3.0中的新端点路由所致。
引用下面的票据。
这是因为在ASP.NET Core 3.0中使用端点路由时:
Mvc将不再向ActionDescriptor添加AuthorizeFilter,而ResourceInvoker也不会调用AuthorizeAsync()https://github.com/aspnet/AspNetCore/blob/90ab2cb965aeb8ada13bc4b936b3735ca8dd28df/src/Mvc/Mvc.Core/src/ApplicationModels/AuthorizationApplicationModelProvider.cs#L40 Mvc将所有Filter作为元数据添加到endpoint.Metadatahttps://github.com/aspnet/AspNetCore/blob/5561338cfecac5ca4b1dda2f09a7f66153d0b5fe/src/Mvc/Mvc.Core/src/Routing/ActionEndpointFactory.cs#L348 而是通过AuthorizationMiddleware调用AuthorizeAsync()和resouorce是Endpointhttps://github.com/aspnet/AspNetCore/blob/5561338cfecac5ca4b1dda2f09a7f66153d0b5fe/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs#L63 新方法。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CookieOrTokenAuthorizationRequirement requirement)
{
    if (context.Resource is Endpoint endpoint)
    {
        if (endpoint.Metadata.OfType<IFilterMetadata>().Any(filter => filter is MyFilter))
        {
            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }
}

https://github.com/dotnet/aspnetcore/issues/11075

值得注意的是,使用新的上下文,您将无法像以前使用AuthorizationFilterContext那样访问路由数据。您需要将IHttpContextAccessor注入到AuthorizationHandler中。

// Ensure your handler is registered as scoped
services.AddScoped<IAuthorizationHandler, InvestorRequirementHandler>();


public class InvestorRequirementHandler : AuthorizationHandler<InvestorRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public InvestorRequirementHandler(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, InvestorRequirement requirement)
    {
        var tenant = httpContextAccessor.HttpContext.GetRouteData().Values[ExceptionHandlerMiddleware.TenantCodeKey].ToString();
    }
}

5
似乎这在 dotnet 5.0 不再起作用了。context.Resource 现在是类型为 DefaultHttpContext。你有任何想法如何从中获取当前的端点(Endpoint)吗? - Robba
2
@Robba DefaultHttpContext 包含一个 GetEndPoint() 扩展方法。 - Cyril Durand
2
@CyrilDurand 进一步的调查揭示了这一点。仍然有点奇怪,对于 .net core 2.0、3.0 和 5.0,需要对 context.Resource 进行三个不同的转换。 - Robba
对于 .NET 5,context.Resource 始终为 null,有人可能会使用 (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext),然后调用 httpContext.GetEndpoint()。 - Imran Javed

6
public class CanEditOnlyOtherAdminRolesAndClaimsHandler :
            AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
{
    private readonly IHttpContextAccessor httpContextAccessor;
    public CanEditOnlyOtherAdminRolesAndClaimsHandler(
            IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor;    
    }

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
            ManageAdminRolesAndClaimsRequirement requirement)
    {

        var loggedInAdminId = context.User.Claims
            .FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value.ToString();

        var adminIdBeingEdited = httpContextAccessor.HttpContext
            .Request.Query["userId"].ToString();

        if (context.User.IsInRole("Admin")
             && context.User.HasClaim(c => c.Type == "Edit Role" && c.Value == "true")
             && adminIdBeingEdited.ToLower() != loggedInAdminId.ToLower())
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }

}

2
public class CanEditOnlyOtherAdminRolesAndClaimsHandler :
   AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
    {
       private readonly IHttpContextAccessor httpContextAccessor;

       public CanEditOnlyOtherAdminRolesAndClaimsHandler(IHttpContextAccessor httpContextAccessor)
       {
            this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
       }

       protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
       ManageAdminRolesAndClaimsRequirement requirement)
        {
            if (context.User == null || !context.User.Identity.IsAuthenticated)
            {
                context.Fail();
                return Task.CompletedTask;
            }

            string loggedInAdminId =
                context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;

            string adminIdBeingEdited = httpContextAccessor.HttpContext.Request.Query["userId"].ToString();

            if (context.User.IsInRole("Admin") &&
            context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
            adminIdBeingEdited.ToLower() != loggedInUserId.ToLower())
            {
                context.Succeed(requirement);
            }

            return Task.CompletedTask;
        }
    }
}

然后在 Startup 类的 ConfigureServices 方法中添加以下服务:

 public void ConfigureServices(IServiceCollection services)
        {           
          services.AddAuthorization(options =>
            {
             options.AddPolicy("ManageRolesPolicy", policy => policy.Requirements.Add(new ManageAdminRolesAndClaimsRequirement()));
            }

          services.AddScoped<IAuthorizationHandler, CanEditOnlyOtherAdminRolesAndClaimsHandler>();       
      }

如果您想处理多个自定义授权需求:
CanEditOnlyOtherAdminRolesAndClaimsHandler类中,您需要检查用户是否属于Admin角色并拥有Edit Role的权限。假设您要求用户必须属于Super Admin角色,在这种情况下,您可以选择以下两种方法之一:
- 编辑CanEditOnlyOtherAdminRolesAndClaimsHandler类中的条件,如下所示:
if (context.User.IsInRole("Admin") &&
                context.User.HasClaim(claim => claim.Type == "Edit Role" && claim.Value == "true") &&
                adminIdBeingEdited.ToLower() != loggedInUserId.ToLower() || 
                context.User.IsInRole("Super Admin") && adminIdBeingEdited.ToLower() != loggedInUserId.ToLower())
                {
                    context.Succeed(requirement);
                }

- 或者为新需求自定义另一个授权处理程序,这种情况下是超级管理员角色:

创建一个名为ManageRolesAndClaimsSuperAdminHandler的新类,该类的实现应如下所示:

public class ManageRolesAndClaimsSuperAdminHandler : AuthorizationHandler<ManageAdminRolesAndClaimsRequirement>
    {
        private readonly IHttpContextAccessor httpContextAccessor;

        public ManageUsersRolesSuperAdminHandler(IHttpContextAccessor httpContextAccessor)
        {
            this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
        }

        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ManageAdminRolesAndClaimsRequirement  requirement)
        {
        if (context.User == null || !context.User.Identity.IsAuthenticated)
            {
                context.Fail();
                return Task.CompletedTask;
            }

            string loggedInAdminId = context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier).Value;

            string adminIdBeingEdited = httpContextAccessor.HttpContext.Request.Query["userId"].ToString();

            if (context.User.IsInRole("Super Admin") && adminIdBeingEdited.ToLower() != loggedInUserId.ToLower())
            {
                context.Succeed(requirement);
            }
            return Task.CompletedTask;
        }
    }

现在在Startup类的ConfigureServices方法中注册新的处理程序。

public void ConfigureServices(IServiceCollection services)
      {        
          services.AddAuthorization(options =>
            {
             options.AddPolicy("ManageRolesPolicy", policy => policy.Requirements.Add(new ManageAdminRolesAndClaimsRequirement()));
            }

          services.AddScoped<IAuthorizationHandler, CanEditOnlyOtherAdminRolesAndClaimsHandler>();
          services.AddScoped<IAuthorizationHandler, ManageRolesAndClaimsSuperAdminHandler>();
      }

1
如果您在控制器中使用Rout属性,则需要更改startup.cs,您可以将其替换为
  app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
               name: "home",
               pattern: "{controller=Home}");
        });

在Configure()函数中使用此语句。
        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "home",
                template: "{controller=Home}");
        });

您可以在ConfigureSevices()中使用此方法禁用端点路由。

   services.AddMvc().AddMvcOptions(mvcopt=> { mvcopt.EnableEndpointRouting = false;});

也适用于 Asp .Net Core 5


有没有办法在“app.UseEndpoints”中使用? - Ali reza Soleimani Asl

0

只有在 [Authorize] 属性的上下文中,Resource 属性才会是 AuthorizationFilterContext。


0
    private readonly IHttpContextAccessor httpContextAccessor;
    public CanEditOnlyOtherAdminRolesAndClaimsHandler(IHttpContextAccessor httpContextAccessor)
    {
        this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));

    }

请提供您的解决方案是如何工作的说明。 - Selim Yildiz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接