ASP.NET Core 1.0 MVC中如何覆盖全局授权过滤器

27

我正在尝试在ASP.NET Core 1.0(MVC 6)Web应用程序中设置授权。

更为严格的方法-默认情况下,我希望将所有控制器和操作方法限制为具有Admin角色的用户。 因此,我添加了一个全局授权属性,例如:

AuthorizationPolicy requireAdminRole = new AuthorizationPolicyBuilder()
    .RequireAuthenticatedUser()
    .RequireRole("Admin")
    .Build();
services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(requireAdminRole));});

我想允许特定角色的用户访问具体的控制器。例如:

[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}
当然这是行不通的,因为“全局过滤器”不允许UserManager以非“管理员”的身份访问控制器。

在MVC5中,我可以通过创建自定义授权属性并将逻辑放置在那里来实现此目的。 然后将此自定义属性用作全局属性。例如:

public class IsAdminOrAuthorizeAttribute : AuthorizeAttribute
{
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        ActionDescriptor action = filterContext.ActionDescriptor;
        if (action.IsDefined(typeof(AuthorizeAttribute), true) ||
            action.ControllerDescriptor.IsDefined(typeof(AuthorizeAttribute), true))
        {
            return;
        }

        base.OnAuthorization(filterContext);
    }
}

我尝试创建一个自定义的AuthorizeFilter,但没有成功。API似乎与以前不同。

所以我的问题是:是否可以设置默认策略,然后针对特定的控制器和操作进行覆盖。或者类似的东西。我不想这样做

[Authorize(Roles="Admin,[OtherRoles]")]

在每个控制器/操作上都需要进行此操作,因为这是一个潜在的安全问题。如果我不小心忘记放置Admin角色会发生什么。

2个回答

37
您需要对框架进行一些调整,因为您的全局策略比要应用于特定控制器和操作的策略更加严格:
  • 默认情况下,只有 管理员 用户可以访问您的应用程序
  • 某些角色也将被授予访问某些控制器的权限(例如 UserManagers 访问 UsersController
正如您已经注意到的那样,具有全局过滤器意味着只有 管理员 用户才能访问控制器。当您在 UsersController 上添加附加属性时,只有同时是 管理员UserManager 的用户才能访问。
可以使用类似于 MVC 5 的方法,但其工作方式不同。
  • 在MVC 6中,[Authorize]属性不包含授权逻辑。
  • 相反,AuthorizeFilter是具有OnAuthorizeAsync方法的类,调用授权服务以确保策略得到满足。
  • 使用特定的IApplicationModelProvider为每个带有[Authorize]属性的控制器和操作添加AuthorizeFilter

一个选项是重新创建你的IsAdminOrAuthorizeAttribute,但这次作为一个AuthorizeFilter,然后将其添加为全局过滤器:

public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext context)
    {
        // If there is another authorize filter, do nothing
        if (context.Filters.Any(item => item is IAsyncAuthorizationFilter && item != this))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});

这将仅在控制器/操作没有特定的[Authorize]属性时应用全局过滤器。
你可以通过注入自己到生成每个控制器和操作应用的过滤器的过程中,避免使用全局过滤器。你可以添加自己的 IApplicationModelProvider 或者自己的 IApplicationModelConvention。两者都允许你添加/删除特定的控制器和操作过滤器。
例如,你可以定义一个默认的授权策略和额外的特定策略:
services.AddAuthorization(opts =>
{
    opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
    opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});

然后您可以创建一个新的,将默认策略添加到每个没有自己的[Authorize]属性的控制器中(应用程序约定非常相似,可能更符合框架扩展的方式。我只是快速使用现有的AuthorizationApplicationModelProvider作为指南):
public class OverridableDefaultAuthorizationApplicationModelProvider : IApplicationModelProvider
{
    private readonly AuthorizationOptions _authorizationOptions;

    public OverridableDefaultAuthorizationApplicationModelProvider(IOptions<AuthorizationOptions> authorizationOptionsAccessor)
    {
        _authorizationOptions = authorizationOptionsAccessor.Value;
    }

    public int Order
    {
        //It will be executed after AuthorizationApplicationModelProvider, which has order -990
        get { return 0; }
    }

    public void OnProvidersExecuted(ApplicationModelProviderContext context)
    {
        foreach (var controllerModel in context.Result.Controllers)
        {
            if (controllerModel.Filters.OfType<IAsyncAuthorizationFilter>().FirstOrDefault() == null)
            {
                //default policy only used when there is no authorize filter in the controller
                controllerModel.Filters.Add(new AuthorizeFilter(_authorizationOptions.DefaultPolicy));
            }
        }
    }

    public void OnProvidersExecuting(ApplicationModelProviderContext context)
    {            
        //empty    
    }
}

//Register in Startup.ConfigureServices
services.TryAddEnumerable(
    ServiceDescriptor.Transient<IApplicationModelProvider, OverridableDefaultAuthorizationApplicationModelProvider>());

有了这个设置,这两个控制器将使用默认策略:

public class FooController : Controller

[Authorize]
public class BarController : Controller

这里将使用具体的用户策略:

[Authorize(Policy = "Users")]
public class UsersController : Controller

请注意,您仍需要将管理员角色添加到每个策略中,但至少所有策略都将在单个启动方法中声明。您可能可以创建自己的方法来构建策略,这将始终添加管理员角色。

哈哈,感谢详细的解释。正是我想要实现的。我知道我可以重写 AuthorizeFilter,但只是没有付出足够的努力去做。对于 IApplicationModelProvider,我不知道它的存在。刚刚实现了 IApplicationModelProvider 的方法,可以确认它正在工作。我认为这更符合新的 MVC 认证模型的精神,所以我会采用它。 - regnauld
我也建议尝试使用 IApplicationModelConvention,甚至可以在 services.AddMvc() 扩展方法中添加选项。关于代码的主要区别是您需要将身份验证选项传递给它。 - Daniel J.G.
这对我没起作用。我正在尝试使用过滤器的方法,在我的OnAuthorizationAsync方法中,上下文中总是有另外两个过滤器,所以IsAdminOrAuthorized过滤器从来没有运行过。甚至在我的应用程序中没有任何策略/AuthorizeAttributes。 - TarkaDaal
谢谢,那个答案对我解决另一个问题很有帮助 ;) - Jarzyn

1

使用 @Daniel 的解决方案时,我遇到了评论中提到的与 @TarkaDaal 相同的问题(每次调用上下文中都有 2 个 AuthorizeFilter...不太确定它们来自哪里)。

因此,我解决问题的方法如下:

public class IsAdminOrAuthorizeFilter : AuthorizeFilter
{
    public IsAdminOrAuthorizeFilter(AuthorizationPolicy policy): base(policy)
    {
    }

    public override Task OnAuthorizationAsync(Microsoft.AspNet.Mvc.Filters.AuthorizationContext context)
    {
        if (context.Filters.Any(f =>
        {
            var filter = f as AuthorizeFilter;
            //There's 2 default Authorize filter in the context for some reason...so we need to filter out the empty ones
            return filter?.AuthorizeData != null && filter.AuthorizeData.Any() && f != this;
        }))
        {
            return Task.FromResult(0);
        }

        //Otherwise apply this policy
        return base.OnAuthorizationAsync(context);
    }        
}

services.AddMvc(opts => 
{
    opts.Filters.Add(new IsAdminOrAuthorizeFilter(new AuthorizationPolicyBuilder().RequireRole("admin").Build()));
});

这虽然不太美观,但在这种情况下它是有效的,因为如果你只使用没有参数的Authorize属性,你仍然会被new AuthorizationPolicyBuilder().RequireRole("admin").Build()过滤器处理。

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