【问题标题】:"context.Resource as AuthorizationFilterContext" returning null in ASP.NET Core 3.0“context.Resource as AuthorizationFilterContext”在 ASP.NET Core 3.0 中返回 null
【发布时间】:2020-03-30 12:45:26
【问题描述】:

我正在尝试按照教程实施自定义授权要求。这好像是 context.Resource 不再包含 AuthorizationFilterContext,因此:

var authFilterContext = context.Resource as AuthorizationFilterContext;

返回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 中解决这个问题?

【问题讨论】:

标签: c# asp.net


【解决方案1】:
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】:

    startup.cs 的变化,如果你在控制器中使用 Rout 属性 你可以替换这个

      app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                   name: "home",
                   pattern: "{controller=Home}");
            });
    

    在 Configure() 中使用这个

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "home",
                    template: "{controller=Home}");
            });
    

    并在 ConfigureServices() 中禁用端点路由

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

    也适用于 Asp .Net Core 5

    【讨论】:

      【解决方案3】:
      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);
                      }
      

      - 或为新需求定制另一个授权处理程序,在本例中为Super Admin 角色:

      新建一个类,命名为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>();
            }
      

      【讨论】:

        【解决方案4】:

        这是由于 .NET Core 3.0 中的新端点路由。

        引用下面的票。

        这是因为在 ASP.NET Core 3.0 中使用端点路由时:

        Mvc 将不再将 AuthorizeFilter 添加到 ActionDescriptor 和 ResourceInvoker 不会调用 AuthorizeAsync() https://github.com/aspnet/AspNetCore/blob/90ab2cb965aeb8ada13bc4b936b3735ca8dd28df/src/Mvc/Mvc.Core/src/ApplicationModels/AuthorizationApplicationModelProvider.cs#L40

        Mvc 会将所有 Filter 作为元数据添加到 endpoint.Metadata https://github.com/aspnet/AspNetCore/blob/5561338cfecac5ca4b1dda2f09a7f66153d0b5fe/src/Mvc/Mvc.Core/src/Routing/ActionEndpointFactory.cs#L348

        而是通过 AuthorizationMiddleware 调用 AuthorizeAsync() 和 资源是端点 https://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();
            }
        }
        

        【讨论】:

        • 这似乎不再适用于 dotnet 5.0。 context.Resource 现在是 DefaultHttpContext 类型。知道如何从那里获取当前端点吗?
        • @Robba DefaultHttpContext 包含一个 GetEndPoint() 扩展方法
        • @CyrilDurand 一些进一步的调查确实揭示了这一点。为 .net core 2.0、3.0 和 5.0 执行此操作仍然需要三个不同的 context.Resource
        • 对于 .net 5,context.Resource is Endpoint 始终为 null,有人可能会使用 (context.Resource is Microsoft.AspNetCore.Http.DefaultHttpContext httpContext) 然后 httpContext.GetEndpoint()
        【解决方案5】:

        Resource 属性将仅是 [Authorize] 属性上下文中的 AuthorizationFilterContext。

        【讨论】:

          【解决方案6】:
              private readonly IHttpContextAccessor httpContextAccessor;
              public CanEditOnlyOtherAdminRolesAndClaimsHandler(IHttpContextAccessor httpContextAccessor)
              {
                  this.httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
          
              }
          

          【讨论】:

          • 请解释您的解决方案是如何工作的。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2021-11-21
          • 1970-01-01
          • 2023-03-22
          • 2020-01-23
          • 2019-03-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多