Como redirecionar para um URL de login dinâmico na ASP.NET MVC


96

Estou criando um site multilocação que hospeda páginas para clientes. O primeiro segmento da URL será uma string que identifica o cliente, definida em Global.asax usando o seguinte esquema de roteamento de URL:

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

Isso funciona bem com URLs como / foo / Home / Index.

No entanto, ao usar o atributo [Autorizar], desejo redirecionar para uma página de login que também usa o mesmo esquema de mapeamento. Portanto, se o cliente for foo, a página de login seria / foo / Account / Login em vez do redirecionamento fixo / Account / Login definido em web.config.

O MVC usa um HttpUnauthorizedResult para retornar um status 401 de não autorizado, que presumo que faz com que o ASP.NET redirecione para a página definida em web.config.

Então, alguém sabe como substituir o comportamento de redirecionamento de login do ASP.NET? Ou seria melhor redirecionar em MVC criando um atributo de autorização personalizado?

EDIT - Resposta: após algumas pesquisas na fonte .Net, decidi que um atributo de autenticação personalizado é a melhor solução:

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 }
                });
        }
    }
}

2
fazendo quase exatamente a mesma coisa com o roteamento, então eu precisava disso! Obrigado!
Trevor de Koekkoek

Obrigado, estava tentando descobrir como fazer algo semelhante.
Chance de

me deu ideia para implementação própria, muito obrigado!
Alexander Beletsky

3
certifique-se de definir area = null (ou para a área correta) se estiver usando MVC 2 e superior - ou então será herdado da página que você tentou visitar
Simon_Weaver

Alguma maneira de fazer isso sem MVC?
DARKGuy

Respostas:


30

Eu acho que o principal problema é que se você vai pegar carona na classe ASP.NET FormsAuthentication integrada (e não há uma boa razão para não fazer isso), algo no final do dia vai chamar FormsAuthentication.RedirectToLoginPage()que vai para ver o URL configurado. Há apenas um URL de login, sempre, e é assim que eles o criaram.

Minha tentativa de resolver o problema (possivelmente uma implementação de Rube Goldberg) seria deixá-lo redirecionar para uma única página de login na raiz compartilhada por todos os clientes, digamos / conta / login. Esta página de login não exibiria nada; ele inspeciona o parâmetro ReturnUrl ou algum valor que obtive na sessão ou um cookie que identifica o cliente e usa isso para emitir um redirecionamento 302 imediato para a página específica / cliente / conta / login. É um redirecionamento extra, mas provavelmente não perceptível e permite que você use os mecanismos de redirecionamento integrados.

A outra opção é criar seu próprio atributo personalizado conforme você descreve e evita qualquer coisa que chame o RedirectToLoginPage()método na FormsAuthenticationclasse, já que você o substituirá por sua própria lógica de redirecionamento. (Você pode criar sua própria classe semelhante.) Como é uma classe estática, não conheço nenhum mecanismo pelo qual você possa simplesmente injetar sua própria interface alternativa e fazê-la funcionar magicamente com o atributo [Autorizar] existente, que golpes, mas as pessoas já fizeram coisas semelhantes antes .

Espero que ajude!


esta é provavelmente a abordagem mais segura. criar seu próprio atributo [MyAuthorize] é perigoso. a menos que sua compilação esteja verificando se as pessoas não usam o atributo embutido [Autorizar], você corre o risco de as pessoas (ou a si mesmo) esquecerem e usarem o errado
Simon_Weaver

Em alguns casos, pode ser útil substituir Application_AuthenticateRequest(veja minha resposta abaixo).
turdus-merula de

41

Na versão RTM da ASP.NET MVC, a propriedade Cancel está ausente. Este código funciona com 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 }
                        });
            }
        }
    }
}

Edit: Você pode querer desabilitar o loginUrl de autenticação de formulários padrão em web.config - no caso de alguém esquecer que você tem um atributo personalizado e usar o atributo embutido [Autorizar] por engano.

Modifique o valor em web.config:

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

Em seguida, execute um método de ação 'ERROR' que registra um erro e redireciona o usuário para a página de login mais genérica que você possui.


2
certifique-se de adicionar {area, null} ao dicionário (ou qualquer que seja o nome da sua área) se estiver usando MVC 2 e superior - ou então será herdado da página que você tentou visitar
Simon_Weaver

2

Minha solução para esse problema foi uma ActionResultclasse 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; }
    }

0

Ainda assim, se alguém decidir usar o ASP.NET FormsAuthentication embutido, pode substituí-lo Application_AuthenticateRequestda Global.asax.csseguinte maneira:

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;
        }
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.