Cómo redirigir a una URL de inicio de sesión dinámica en ASP.NET MVC

96

Estoy creando un sitio web multicliente que aloja páginas para clientes. El primer segmento de la URL será una cadena que identifica al cliente, definida en Global.asax utilizando el siguiente esquema de enrutamiento de URL:

"{client}/{controller}/{action}/{id}"

Esto funciona bien, con URL como / foo / Home / Index.

Sin embargo, cuando uso el atributo [Autorizar], quiero redirigir a una página de inicio de sesión que también usa el mismo esquema de asignación. Entonces, si el cliente es foo, la página de inicio de sesión sería / foo / Account / Login en lugar de la redirección fija / Account / Login definida en web.config.

MVC usa un HttpUnauthorizedResult para devolver un estado 401 no autorizado, que supongo hace que ASP.NET redirija a la página definida en web.config.

Entonces, ¿alguien sabe cómo anular el comportamiento de redireccionamiento de inicio de sesión de ASP.NET? ¿O sería mejor redirigir en MVC creando un atributo de autorización personalizado?

EDITAR - Respuesta: después de investigar un poco la fuente .Net, decidí que un atributo de autenticación personalizado es la mejor solución:

public class ClientAuthorizeAttribute: AuthorizeAttribute
{
    public override void OnAuthorization( AuthorizationContext filterContext )
    {
        base.OnAuthorization( filterContext );

        if (filterContext.Cancel && filterContext.Result is HttpUnauthorizedResult )
        {
            filterContext.Result = new RedirectToRouteResult(
                new RouteValueDictionary
                {
                    { "client", filterContext.RouteData.Values[ "client" ] },
                    { "controller", "Account" },
                    { "action", "Login" },
                    { "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
                });
        }
    }
}
Mike Scott
fuente
2
haciendo casi exactamente lo mismo con el enrutamiento, ¡así que necesitaba esto! ¡Gracias!
Trevor de Koekkoek
Gracias, estaba tratando de averiguar cómo hacer algo similar.
Chance
me dio una idea para la propia implementación, ¡muchas gracias!
Alexander Beletsky
3
asegúrese de establecer area = null (o en el área correcta) si usa MVC 2 y superior, o de lo contrario se heredará de la página que intentó visitar
Simon_Weaver
¿Alguna forma de hacer esto sin MVC?
DARKGuy

Respuestas:

30

Creo que el problema principal es que si va a aprovechar la clase integrada de ASP.NET FormsAuthentication (y no hay una buena razón por la que no debería hacerlo), algo al final del día llamará a FormsAuthentication.RedirectToLoginPage()cuál para mirar la URL configurada. Solo hay una URL de inicio de sesión, y así es como la diseñaron.

Mi intento por resolver el problema (posiblemente una implementación de Rube Goldberg) sería permitir que se redirija a una única página de inicio de sesión en la raíz compartida por todos los clientes, digamos / account / login. Esta página de inicio de sesión en realidad no mostraría nada; inspecciona el parámetro ReturnUrl o algún valor que tengo en la sesión o una cookie que identifica al cliente y lo usa para emitir una redirección 302 inmediata a la página específica / cliente / cuenta / inicio de sesión. Es una redirección adicional, pero probablemente no se perciba y le permite utilizar los mecanismos de redirección integrados.

La otra opción es crear su propio atributo personalizado como lo describe y evitar cualquier cosa que llame al RedirectToLoginPage()método en la FormsAuthenticationclase, ya que lo reemplazará con su propia lógica de redireccionamiento. (Puede crear su propia clase que sea similar). Dado que es una clase estática, no conozco ningún mecanismo mediante el cual pueda inyectar su propia interfaz alternativa y hacer que funcione mágicamente con el atributo [Autorizar] existente, que golpes, pero la gente ha hecho cosas similares antes .

¡Espero que ayude!

Nicolás Piasecki
fuente
este es probablemente el enfoque más seguro. crear su propio atributo [MyAuthorize] es peligroso. a menos que su compilación verifique que las personas no usan el atributo [Autorizar] incorporado, corre el riesgo de que las personas (o usted mismo) olviden y usen el incorrecto
Simon_Weaver
Sin embargo, en algunos casos podría ser útil anular Application_AuthenticateRequest(consulte mi respuesta a continuación).
turdus-merula
41

En la versión RTM de ASP.NET MVC, falta la propiedad Cancelar. Este código funciona con ASP.NET MVC RTM:

using System;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Resources;

namespace ePegasus.Web.ActionFilters
{
    public class CustomAuthorize : AuthorizeAttribute
    {
        public override void OnAuthorization(AuthorizationContext filterContext)
        {
            base.OnAuthorization(filterContext);
            if (filterContext.Result is HttpUnauthorizedResult)
            {
                filterContext.Result = new RedirectToRouteResult(
                    new System.Web.Routing.RouteValueDictionary
                        {
                                { "langCode", filterContext.RouteData.Values[ "langCode" ] },
                                { "controller", "Account" },
                                { "action", "Login" },
                                { "ReturnUrl", filterContext.HttpContext.Request.RawUrl }
                        });
            }
        }
    }
}

Editar: Es posible que desee deshabilitar la URL de inicio de sesión de autenticación de formularios predeterminada en web.config, en caso de que alguien olvide que tiene un atributo personalizado y use el atributo [Autorizar] integrado por error.

Modifique el valor en web.config:

 <forms loginUrl="~/Account/ERROR" timeout="2880" />

Luego, cree un método de acción 'ERROR' que registre un error y redirija al usuario a la página de inicio de sesión más genérica que tenga.

usuario134936
fuente
2
asegúrese de agregar {area, null} al diccionario (o como se llame su área) si usa MVC 2 y superior, o de lo contrario se heredará de la página que intentó visitar
Simon_Weaver
2

Mi solución a este problema fue una ActionResultclase personalizada :

    sealed public class RequiresLoginResult : ActionResult
    {
        override public void ExecuteResult (ControllerContext context)
        {
            var response = context.HttpContext.Response;

            var url = FormsAuthentication.LoginUrl;
            if (!string.IsNullOrWhiteSpace (url))
                url += "?returnUrl=" + HttpUtility.UrlEncode (ReturnUrl);

            response.Clear ();
            response.StatusCode = 302;
            response.RedirectLocation = url;
        }

        public RequiresLoginResult (string returnUrl = null)
        {
            ReturnUrl = returnUrl;
        }

        string ReturnUrl { get; set; }
    }
Kieron
fuente
0

Aún así, si uno decide usar la autenticación de formularios ASP.NET incorporada, se puede anular Application_AuthenticateRequestde la Global.asax.cssiguiente manera:

protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
    string url = Request.RawUrl;

    if (url.Contains(("Account/Login"))
    {
        return;
    }

    if (Context.User == null)
    {
        // Your custom tenant-aware logic
        if (url.StartsWith("/foo"))
        {
            // Your custom login page.
            Response.Redirect("/foo/Account/Login");
            Response.End();
            return;
        }
    }
}
turdus-merula
fuente