且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

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

更新时间:2023-02-24 20:44:19

由于您的全局策略比您要应用于特定控制器和操作的全局策略更具限制性,因此您将需要使用该框架:

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:

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

您已经知道,拥有全局过滤器意味着只有 Admin 用户可以访问控制器.当您在UsersController上添加其他属性时,只有同时 Admin UserManager 的用户访问.

As you have already noticied, having a global filter means that only Admin users will have access to a controller. When you add the additional attribute on the UsersController, only users that are both Admin and UserManager will have access.

可以使用与MVC 5类似的方法,但是其工作方式不同.

It is possible to use a similar approach to the MVC 5 one, but it works in a different way.

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

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

One option could be to recreate your IsAdminOrAuthorizeAttribute, but this time as an AuthorizeFilter that you will then add as a global filter:

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]属性时,才应用全局过滤器.

This would apply your global filter only when the controller/action doesn't have a specific [Authorize] attribute.

通过将自己注入到生成要应用于每个控制器和操作的过滤器的过程中,您还可以避免使用全局过滤器.您可以添加自己的IApplicationModelProvider或自己的IApplicationModelConvention.两者都可以让您添加/删除特定的控制器和动作过滤器.

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

然后,您可以创建一个新的IApplicatioModelProvider,它将为没有自己的[Authorize]属性的每个控制器添加默认策略(应用程序约定非常相似,并且可能与框架的使用方式更加一致)打算进行扩展.我只是很快地将现有的AuthorizationApplicationModelProvider用作指南):

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):

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. You could probably create your own methods for building policies that will always add the admin role.