2009-06-10 35 views
70

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

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

Si un usuario que no está en la función de Administrador, se desplaza a esta controlador son recibidos con una pantalla en blanco.

Lo que me gustaría hacer es redirigirlos a Vista 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 controlar cada método de acción en IsUserInRole() y si no está en función, entonces regrese esta vista informativa. Sin embargo, tendré que poner eso en cada Acción que rompa el principal DRY y obviamente es engorroso de mantener.

Respuesta

67

crear un atributo de autorización personalizado basado en AuthorizeAttribute y anular OnAuthorization para realizar la comprobación de cómo quiere 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 entradas de blog que entrar en más detalles:

Ejemplo:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] 
    public class MasterEventAuthorizationAttribute : AuthorizeAttribute 
    { 
     /// <summary> 
     /// 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. 
     /// </summary> 
     public virtual string MasterName { get; set; } 

     /// <summary> 
     /// The name of the view to render on authorization failure. Default is "Error". 
     /// </summary> 
     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 */); 
     } 


    } 
+1

Supongo que no hay un enlace al que pueda ir, que lo divide en un poco más fácil de seguir los razonamientos? – Maslow

+1

¿Qué no está claro? Primero usa AuthorizeCore para verificar si el usuario está autorizado y en un rol permitido. De lo contrario, si el usuario no está autenticado, devuelve una respuesta no autorizada estableciendo el resultado en el contexto del filtro. Si se autentica, entonces verifica si está en la función adicional de "SuperUser" (una función predeterminada, no especificada en el atributo). De lo contrario, devuelve un error que indica que, aunque está autorizado, el usuario no tiene un rol válido para la acción. Cuando el usuario está autorizado y tiene un rol válido (o SuperUser), establece la política de caché para evitar el almacenamiento en caché en sentido descendente – tvanfosson

+0

He encontrado una mejor respuesta aquí: http://stackoverflow.com/questions/1498727/asp-net-mvc- how-to-show-desautorizado-error-on-login-page – bluee

1

Debe crear su propio atributo Autorizar-filtro.

Esta es la mía 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 
+0

Esto parece redirigir, pero también parece estar en ejecución de la totalidad del método de acción original primero. –

+0

En lugar de hacer un redireccionamiento, debe hacer 'filterContext.Result = new RedirectResult (loginUrl)' –

5

tuve la 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) 
9

El código por "tvanfosson" se me está dando "Error al ejecutar solicitud secundaria" .. He cambiado el OnAuthorization así:

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 ...

+0

¿Esto devuelve un 401 en la url que el usuario no tiene permiso para hacerlo? – DevDave

22

Puede trabajar con el reemplazable HandleUnauthorizedRequest dentro de su encargo AuthorizeAttribute

De esta manera:

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 puede hacer algo como esto:

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

Ahora se puede utilizar en su método HandleUnauthorizedRequest de esta manera:

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

Este problema me ha perseguido durante algunos días, así que al encontrar la respuesta que afirmativamente funciona 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 básica es la siguiente, dulce y simple:

filterContext.Result = new HttpUnauthorizedResult(); 

En mi caso heredar de un controlador de base, por lo que en cada controlador que herede de ella puedo reemplazar 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. Así que, primero, las dos cosas que no funcionan, de forma inesperada:

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

No sólo los aquellos que no funciona, que no terminen la solicitud tampoco. 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 OnAuthorize el flujo de la lógica 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 inesperadamente, 1) Response.Redirect ("/ login") no funciona: el método de acción todavía 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 filterContext.Result = new HttpUnauthorizedResult() hace?

1

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

Así

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

en lugar de

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

por lo que tendría esto en su controlador:

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

Tal vez se obtiene una página en blanco cuando se ejecuta desde Visual Studio en el servidor de desarrollo que usa la autenticación de Windows (previous topic).

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

<httpErrors> 
    <remove statusCode="401" /> 
    <error statusCode="401" path="/yourapp/error/unauthorized" responseMode="Redirect" /> 
</httpErrors> 

A continuación, cree método ErrorController.Unauthorized y vista personalizada correspondiente.

-1

En sus Startup.Auth.cs FICHA añadir 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<ApplicationUserManager, ApplicationUser>(
     validateInterval: TimeSpan.FromMinutes(30), 
     regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)) 
    } 
}); 
Cuestiones relacionadas