简体   繁体   English

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

[英]Override global authorize filter in ASP.NET Core 1.0 MVC

I am trying to set up authorization in ASP.NET Core 1.0 (MVC 6) web app. 我试图在ASP.NET Core 1.0(MVC 6)Web应用程序中设置授权。

More restrictive approach - by default I want to restrict all controllers and action methods to users with Admin role. 更严格的方法 - 默认情况下,我希望将所有控制器和操作方法限制为具有Admin角色的用户。 So, I am adding a global authorize attribute like: 所以,我正在添加一个全局授权属性,如:

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

Then I want to allow users with specific roles to access concrete controllers. 然后我想允许具有特定角色的用户访问具体的控制器。 For example: 例如:

[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}

Which of course will not work, as the "global filter" will not allow the UserManager to access the controller as they are not "admins". 这当然是行不通的,因为“全局过滤器”不允许UserManager访问控制器,因为它们不是“管理员”。

In MVC5, I was able to implement this by creating a custom authorize attribute and putting my logic there. 在MVC5中,我能够通过创建自定义授权属性并将我的逻辑放在那里来实现这一点。 Then using this custom attribute as a global. 然后使用此自定义属性作为全局。 For example: 例如:

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);
    }
}

I tried to create a custom AuthorizeFilter , but no success. 我试图创建一个自定义AuthorizeFilter ,但没有成功。 API seems to be different. API似乎有所不同。

So my question is: Is it possible to set up default policy and then override it for specific controllers and actions. 所以我的问题是:是否可以设置默认策略,然后为特定控制器和操作覆盖它。 Or something similar. 或类似的东西。 I don't want to go with this 我不想这样做

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

on every controller/action, as this is a potential security problem. 在每个控制器/操作上,因为这是一个潜在的安全问题。 What will happen if I accidentally forget to put the Admin role. 如果我不小心忘记了Admin角色,会发生什么。

You will need to play with the framework a bit since your global policy is more restrictive than the one you want to apply to specific controllers and actions: 您需要稍微使用框架,因为您的全局策略比您想要应用于特定控制器和操作的策略更具限制性:

  • By default only Admin users can access your application 默认情况下,仅管理员用户可以访问您的应用
  • Specific roles will also be granted access to some controllers (like UserManagers accessing the UsersController ) 特定角色还将被授予访问某些控制器的权限(例如访问UsersController UserManagers

As you have already noticied, having a global filter means that only Admin users will have access to a controller. 正如您已经注意到的那样,拥有全局过滤器意味着只有Admin用户才能访问控制器。 When you add the additional attribute on the UsersController , only users that are both Admin and UserManager will have access. 当您添加的附加属性UsersController ,只有均为 admin 的UserManager将拥有访问权限的用户。

It is possible to use a similar approach to the MVC 5 one, but it works in a different way. 可以使用与MVC 5类似的方法,但它以不同的方式工作。

  • In MVC 6 the [Authorize] attribute does not contain the authorization logic. 在MVC 6中, [Authorize]属性不包含授权逻辑。
  • Instead the AuthorizeFilter is the one that has an OnAuthorizeAsync method calling the authorization service to make sure policies are satisfied. 而是AuthorizeFilter是具有OnAuthorizeAsync方法的方法,该方法调用授权服务以确保满足策略。
  • A specific IApplicationModelProvider is used to add an AuthorizeFilter for every controller and action that has an [Authorize] attribute. 特定的IApplicationModelProvider用于为每个具有[Authorize]属性的控制器和操作添加AuthorizeFilter

One option could be to recreate your IsAdminOrAuthorizeAttribute , but this time as an AuthorizeFilter that you will then add as a global filter: 一个选项可能是重新创建您的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()));
});

This would apply your global filter only when the controller/action doesn't have a specific [Authorize] attribute. 仅当控制器/操作没有特定的[Authorize]属性时,才会应用全局过滤器。


You could also avoid having a global filter by injecting yourself in the process that generates the filters to be applied for every controller and action. 您还可以通过在生成要应用于每个控制器和操作的过滤器的过程中注入自己来避免使用全局过滤器。 You can either add your own IApplicationModelProvider or your own IApplicationModelConvention . 您可以添加自己的IApplicationModelProvider或您自己的IApplicationModelConvention Both will let you add/remove specific controller and actions filters. 两者都允许您添加/删除特定的控制器和操作过滤器。

For example, you can define a default authorization policy and extra specific policies: 例如,您可以定义默认授权策略和其他特定策略:

services.AddAuthorization(opts =>
{
    opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
    opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});

Then you can create a new IApplicatioModelProvider that will add the default policy to every controller that doesn't have its own [Authorize] attribute (An application convention would be very similar and probably more aligned with the way the framework is intended to be extended. I just quickly used the existing AuthorizationApplicationModelProvider as a guide): 然后,您可以创建一个新的IApplicatioModelProvider ,它将默认策略添加到没有自己的[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>());

With this in place, the default policy will be used on these 2 controllers: 有了这个,默认策略将用于这两个控制器:

public class FooController : Controller

[Authorize]
public class BarController : Controller

And the specific Users policy will be used here: 此处将使用特定的用户策略:

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

Notice that you still need to add the admin role to every policy, but at least all your policies will be declared in a single startup method. 请注意,您仍需要将admin角色添加到每个策略,但至少所有策略都将在单个启动方法中声明。 You could probably create your own methods for building policies that will always add the admin role. 您可以创建自己的方法来构建始终添加管理角色的策略。

Using @Daniel's solution I ran into the same issue mentioned by @TarkaDaal in the comment (there's 2 AuthorizeFilter in the context for each call...not quite sure where they are coming from). 使用@Daniel的解决方案我在评论中遇到了@TarkaDaal提到的同一个问题(每个调用的上下文中有2个AuthorizeFilter ......不太确定它们来自哪里)。

So my way to solve it is as follow: 所以我的解决方法如下:

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()));
});

This is ugly but it works in this case because if you're only using the Authorize attribute with no arguments you're going to be handled by the new AuthorizationPolicyBuilder().RequireRole("admin").Build() filter anyway. 这很丑陋,但它适用于这种情况,因为如果你只使用没有参数的Authorize属性,你将由new AuthorizationPolicyBuilder().RequireRole("admin").Build()处理new AuthorizationPolicyBuilder().RequireRole("admin").Build()过滤器无论如何。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM