且构网

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

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

更新时间:2023-02-24 20:57:18

您需要稍微使用一下该框架,因为您的全局策略比您要应用于特定控制器和操作的策略更严格:

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] 属性不包含授权逻辑.
  • 取而代之的是 AuthorizeFilter 是具有调用授权服务以确保满足策略的 OnAuthorizeAsync 方法的方法.
  • 具体的IApplicationModelProvider 用于为每个具有 [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>());

有了这个,默认策略将在这 2 个控制器上使用:

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.