c# - Reemplace el filtro de autorización global en ASP.NET Core MVC 1.0
asp.net-core asp.net-core-mvc (2)
Estoy intentando configurar la autorización en la aplicación web ASP.NET Core 1.0 (MVC 6).
Enfoque más restrictivo: por defecto, quiero restringir todos los controladores y métodos de acción a los usuarios con rol de Admin
. Por lo tanto, estoy agregando un atributo de autorización global como:
AuthorizationPolicy requireAdminRole = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.RequireRole("Admin")
.Build();
services.AddMvc(options => { options.Filters.Add(new AuthorizeFilter(requireAdminRole));});
Entonces quiero permitir que los usuarios con roles específicos accedan a controladores concretos. Por ejemplo:
[Authorize(Roles="Admin,UserManager")]
public class UserControler : Controller{}
Lo que, por supuesto, no funcionará, ya que el "filtro global" no permitirá que el UserManager
acceda al controlador ya que no son "administradores".
En MVC5, pude implementar esto creando un atributo de autorización personalizado y poniendo mi lógica allí. A continuación, utilizando este atributo personalizado como un global. Por ejemplo:
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);
}
}
Intenté crear un AuthorizeFilter
personalizado, pero no tuve éxito. API parece ser diferente
Así que mi pregunta es: ¿es posible configurar la política predeterminada y luego anularla para controladores y acciones específicos? O algo similar. No quiero ir con esto
[Authorize(Roles="Admin,[OtherRoles]")]
en cada controlador / acción, ya que este es un problema de seguridad potencial. ¿Qué sucederá si accidentalmente me olvido de poner el rol de Admin
?
Deberá jugar con el marco un poco, ya que su política global es más restrictiva que la que desea aplicar a controladores y acciones específicos:
- Por defecto, solo los usuarios administradores pueden acceder a su aplicación.
- A los roles específicos también se les otorgará acceso a algunos controladores (como los UserManagers que acceden al
UsersController
)
Como ya notó, tener un filtro global significa que solo los usuarios del administrador tendrán acceso a un controlador. Cuando agrega el atributo adicional en el UsersController
, solo los usuarios que son Administradores y UserManager tendrán acceso.
Es posible utilizar un enfoque similar al de MVC 5, pero funciona de una manera diferente.
- En MVC 6, el atributo
[Authorize]
no contiene la lógica de autorización. - En su lugar,
AuthorizeFilter
es el que tiene un métodoOnAuthorizeAsync
que llama al servicio de autorización para asegurarse de que se cumplan las políticas. - Se
IApplicationModelProvider
unIApplicationModelProvider
específico para agregar unAuthorizeFilter
para cada controlador y acción que tiene un atributo[Authorize]
.
Una opción podría ser recrear su IsAdminOrAuthorizeAttribute
, pero esta vez como un AuthorizeFilter
que luego agregará como un filtro global:
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()));
});
Esto aplicaría su filtro global solo cuando el controlador / acción no tenga un atributo [Authorize]
específico.
También puede evitar tener un filtro global si se inyecta en el proceso que genera los filtros que se aplicarán a cada controlador y acción. Puede agregar su propio IApplicationModelProvider
o su propio IApplicationModelConvention
. Ambos te permitirán agregar / eliminar filtros de acciones y controladores específicos.
Por ejemplo, puede definir una política de autorización predeterminada y políticas específicas adicionales:
services.AddAuthorization(opts =>
{
opts.DefaultPolicy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().RequireRole("admin").Build();
opts.AddPolicy("Users", policy => policy.RequireAuthenticatedUser().RequireRole("admin", "users"));
});
Luego, puede crear un nuevo IApplicatioModelProvider
que agregará la política predeterminada a cada controlador que no tenga su propio atributo [Authorize]
(una convención de aplicación sería muy similar y probablemente más alineada con la forma en que se pretende extender el marco. Simplemente utilicé rápidamente el AuthorizationApplicationModelProvider
existente como una guía):
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>());
Con esto en su lugar, la política predeterminada se utilizará en estos 2 controladores:
public class FooController : Controller
[Authorize]
public class BarController : Controller
Y la política específica de Usuarios será utilizada aquí:
[Authorize(Policy = "Users")]
public class UsersController : Controller
Tenga en cuenta que aún necesita agregar el rol de administrador a cada política, pero al menos todas sus políticas se declararán en un solo método de inicio. Probablemente podría crear sus propios métodos para crear políticas que siempre agreguen el rol de administrador.
Usando la solución de @ Daniel encontré el mismo problema mencionado por @TarkaDaal en el comentario (hay 2 AuthorizeFilter
en el contexto para cada llamada ... no estoy muy seguro de dónde vienen).
Así que mi manera de resolverlo es como sigue:
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()));
});
Esto es feo, pero funciona en este caso porque si solo está utilizando el atributo Autorizar sin argumentos, el new AuthorizationPolicyBuilder().RequireRole("admin").Build()
filtrará de todos modos.