Redirigir controlador no autorizado en ASP.NET MVC

Tengo un controlador en ASP.NET MVC que he restringido a la función de administrador:

[Authorize(Roles = "Admin")] public class TestController : Controller { ... 

Si un usuario que no está en la función Admin navega hacia este controlador, se le saluda con una pantalla en blanco.

Lo que me gustaría hacer es redirigirlos a Ver que dice “necesitas estar en la función de administrador para poder acceder a este recurso”.

Una forma de hacer esto en la que he pensado es verificar cada método de acción en IsUserInRole () y, si no está en el rol, devolver esta vista informativa. Sin embargo, tendré que poner eso en cada Acción que rompa el principal DRY y obviamente es engorroso de mantener.

Cree un atributo de autorización personalizado basado en AuthorizeAttribute y anule OnAuthorization para realizar la comprobación de cómo desea que se haga. Normalmente, AuthorizeAttribute establecerá el resultado del filtro en HttpUnauthorizedResult si falla la verificación de autorización. Podría hacer que lo establezca en un ViewResult (de su vista de Error) en su lugar.

EDITAR : Tengo un par de publicaciones en el blog que entran en más detalle:

Ejemplo:

  [AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )] public class MasterEventAuthorizationAttribute : AuthorizeAttribute { ///  /// The name of the master page or view to use when rendering the view on authorization failure. Default /// is null, indicating to use the master page of the specified view. ///  public virtual string MasterName { get; set; } ///  /// The name of the view to render on authorization failure. Default is "Error". ///  public virtual string ViewName { get; set; } public MasterEventAuthorizationAttribute() : base() { this.ViewName = "Error"; } protected void CacheValidateHandler( HttpContext context, object data, ref HttpValidationStatus validationStatus ) { validationStatus = OnCacheAuthorization( new HttpContextWrapper( context ) ); } public override void OnAuthorization( AuthorizationContext filterContext ) { if (filterContext == null) { throw new ArgumentNullException( "filterContext" ); } if (AuthorizeCore( filterContext.HttpContext )) { SetCachePolicy( filterContext ); } else if (!filterContext.HttpContext.User.Identity.IsAuthenticated) { // auth failed, redirect to login page filterContext.Result = new HttpUnauthorizedResult(); } else if (filterContext.HttpContext.User.IsInRole( "SuperUser" )) { // is authenticated and is in the SuperUser role SetCachePolicy( filterContext ); } else { ViewDataDictionary viewData = new ViewDataDictionary(); viewData.Add( "Message", "You do not have sufficient privileges for this operation." ); filterContext.Result = new ViewResult { MasterName = this.MasterName, ViewName = this.ViewName, ViewData = viewData }; } } protected void SetCachePolicy( AuthorizationContext filterContext ) { // ** IMPORTANT ** // Since we're performing authorization at the action level, the authorization code runs // after the output caching module. In the worst case this could allow an authorized user // to cause the page to be cached, then an unauthorized user would later be served the // cached page. We work around this by telling proxies not to cache the sensitive page, // then we hook our custom authorization code into the caching mechanism so that we have // the final say on whether a page should be served from the cache. HttpCachePolicyBase cachePolicy = filterContext.HttpContext.Response.Cache; cachePolicy.SetProxyMaxAge( new TimeSpan( 0 ) ); cachePolicy.AddValidationCallback( CacheValidateHandler, null /* data */); } } 

Puede trabajar con HandleUnauthorizedRequest HandleUnauthorizedRequest dentro de su HandleUnauthorizedRequest personalizado

Me gusta esto:

 protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext) { // Returns HTTP 401 by default - see HttpUnauthorizedResult.cs. filterContext.Result = new RedirectToRouteResult( new RouteValueDictionary { { "action", "YourActionName" }, { "controller", "YourControllerName" }, { "parameterName", "YourParameterValue" } }); } 

También puedes hacer algo como esto:

 private class RedirectController : Controller { public ActionResult RedirectToSomewhere() { return RedirectToAction("Action", "Controller"); } } 

Ahora puede usarlo en su método HandleUnauthorizedRequest esta manera:

 filterContext.Result = (new RedirectController()).RedirectToSomewhere(); 

El código de “tvanfosson” me daba “Error al ejecutar Child Request”. He cambiado la OnAuthorization de esta manera:

 public override void OnAuthorization(AuthorizationContext filterContext) { base.OnAuthorization(filterContext); if (!_isAuthorized) { filterContext.Result = new HttpUnauthorizedResult(); } else if (filterContext.HttpContext.User.IsInRole("Administrator") || filterContext.HttpContext.User.IsInRole("User") || filterContext.HttpContext.User.IsInRole("Manager")) { // is authenticated and is in one of the roles SetCachePolicy(filterContext); } else { filterContext.Controller.TempData.Add("RedirectReason", "You are not authorized to access this page."); filterContext.Result = new RedirectResult("~/Error"); } } 

Esto funciona bien y muestro la TempData en la página de error. Gracias a “tvanfosson” por el fragmento de código. Estoy usando la autenticación de Windows y _isAuthorized no es más que HttpContext.User.Identity.IsAuthenticated …

Tuve el mismo problema. En lugar de descubrir el código MVC, opté por un truco barato que parece funcionar. En mi clase Global.asax:

 member x.Application_EndRequest() = if x.Response.StatusCode = 401 then let redir = "?redirectUrl=" + Uri.EscapeDataString x.Request.Url.PathAndQuery if x.Request.Url.LocalPath.ToLowerInvariant().Contains("admin") then x.Response.Redirect("/Login/Admin/" + redir) else x.Response.Redirect("/Login/Login/" + redir) 

Este problema me ha perseguido durante algunos días, así que al encontrar la respuesta que funciona afirmativamente con la respuesta de tvanfosson anterior, pensé que valdría la pena enfatizar la parte central de la respuesta y abordar algunas capturas relacionadas.

La respuesta central es esta, dulce y simple:

 filterContext.Result = new HttpUnauthorizedResult(); 

En mi caso, heredo de un controlador base, así que en cada controlador que herede de él anulo OnAuthorize:

 protected override void OnAuthorization(AuthorizationContext filterContext) { base.OnAuthorization(filterContext); YourAuth(filterContext); // do your own authorization logic here } 

El problema fue que en ‘YourAuth’, probé dos cosas que pensé que no solo funcionarían, sino que también terminarían inmediatamente la solicitud. Bueno, así no es como funciona. Entonces, primero, las dos cosas que NO funcionan, inesperadamente:

 filterContext.RequestContext.HttpContext.Response.Redirect("/Login"); // doesn't work! FormsAuthentication.RedirectToLoginPage(); // doesn't work! 

No solo no funcionan, tampoco terminan la solicitud. Lo que significa lo siguiente:

 if (!success) { filterContext.Result = new HttpUnauthorizedResult(); } DoMoreStuffNowThatYouThinkYourAuthorized(); 

Bueno, incluso con la respuesta correcta anterior, ¡el flujo de la lógica aún continúa! Seguirá presionando DoMoreStuff … dentro de OnAuthorize. Así que tenlo en cuenta (DoMore … debería estar en otro modo por lo tanto).

Pero con la respuesta correcta, mientras el flujo de lógica de OnAuthorize continúa hasta el final, después de eso realmente obtienes lo que esperas: un redireccionamiento a tu página de inicio de sesión (si tienes uno configurado en Forms auth en tu webconfig).

Pero de forma inesperada, 1) Response.Redirect (“/ Login”) no funciona: el método de Acción aún se llama, y ​​2) FormsAuthentication.RedirectToLoginPage (); hace lo mismo: ¡el método de Acción todavía se llama!

Lo cual me parece totalmente incorrecto, particularmente con este último: ¿quién hubiera pensado que FormsAuthentication.RedirectToLoginPage no finaliza la solicitud, o no hace el equivalente anterior de lo que hace filterContext.Result = new HttpUnauthorizedResult ()?

Debe construir su propio atributo Autorizar-filtro.

Aquí está el mío para estudiar;)

 Public Class RequiresRoleAttribute : Inherits ActionFilterAttribute Private _role As String Public Property Role() As String Get Return Me._role End Get Set(ByVal value As String) Me._role = value End Set End Property Public Overrides Sub OnActionExecuting(ByVal filterContext As System.Web.Mvc.ActionExecutingContext) If Not String.IsNullOrEmpty(Me.Role) Then If Not filterContext.HttpContext.User.Identity.IsAuthenticated Then Dim redirectOnSuccess As String = filterContext.HttpContext.Request.Url.AbsolutePath Dim redirectUrl As String = String.Format("?ReturnUrl={0}", redirectOnSuccess) Dim loginUrl As String = FormsAuthentication.LoginUrl + redirectUrl filterContext.HttpContext.Response.Redirect(loginUrl, True) Else Dim hasAccess As Boolean = filterContext.HttpContext.User.IsInRole(Me.Role) If Not hasAccess Then Throw New UnauthorizedAccessException("You don't have access to this page. Only " & Me.Role & " can view this page.") End If End If Else Throw New InvalidOperationException("No Role Specified") End If End Sub End Class 

Hubiera dejado esto como un comentario, pero necesito más representantes, de todos modos solo quería mencionarle a Nicholas Peterson que tal vez pasar el segundo argumento a la llamada de redireccionamiento para decirle que finalice la respuesta hubiera funcionado. No es la forma más elegante de manejar esto, pero de hecho funciona.

Asi que

 filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true); 

en lugar de

 filterContext.RequestContext.HttpContext.Response.Redirect("/Login); 

Entonces tendrías esto en tu controlador:

  protected override void OnAuthorization(AuthorizationContext filterContext) { if(!User.IsInRole("Admin") { base.OnAuthorization(filterContext); filterContext.RequestContext.HttpContext.Response.Redirect("/Login", true); } } 

Quizás obtenga una página en blanco cuando se ejecute desde Visual Studio en el servidor de desarrollo usando la autenticación de Windows ( tema anterior ).

Si implementa en IIS, puede configurar páginas de error personalizadas para códigos de estado específicos, en este caso 401. Agregue httpErrors en system.webServer:

     

A continuación, cree ErrorController. Método no autorizado y vista personalizada correspondiente.

En su archivo Startup.Auth.cs agregue esta línea:

 LoginPath = new PathString("/Account/Login"), 

Ejemplo:

 // Enable the application to use a cookie to store information for the signed in user // and to use a cookie to temporarily store information about a user logging in with a third party login provider // Configure the sign in cookie app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, LoginPath = new PathString("/Account/Login"), Provider = new CookieAuthenticationProvider { // Enables the application to validate the security stamp when the user logs in. // This is a security feature which is used when you change a password or add an external login to your account. OnValidateIdentity = SecurityStampValidator.OnValidateIdentity( validateInterval: TimeSpan.FromMinutes(30), regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) } });