Jak przekierować do dynamicznego adresu URL logowania w ASP.NET MVC

96

Tworzę witrynę internetową dla wielu dzierżawców, na której znajdują się strony dla klientów. Pierwszy segment adresu URL będzie ciągiem identyfikującym klienta, zdefiniowanym w Global.asax przy użyciu następującego schematu routingu adresu URL:

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

Działa to dobrze w przypadku adresów URL, takich jak / foo / Home / Index.

Jednak gdy używam atrybutu [Authorize], chcę przekierować do strony logowania, która również używa tego samego schematu mapowania. Więc jeśli klient to foo, strona logowania będzie miała postać / foo / Account / Login zamiast stałego / Account / Login przekierowania zdefiniowanego w web.config.

MVC używa HttpUnauthorizedResult, aby zwrócić nieautoryzowany stan 401, który, jak przypuszczam, powoduje przekierowanie ASP.NET do strony zdefiniowanej w web.config.

Czy ktoś wie, jak zastąpić zachowanie przekierowania logowania ASP.NET? A może lepiej byłoby przekierować w MVC, tworząc niestandardowy atrybut autoryzacji?

EDYCJA - Odpowiedź: po zagłębieniu się w źródło .Net zdecydowałem, że niestandardowy atrybut uwierzytelniania jest najlepszym rozwiązaniem:

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
źródło
2
robi prawie to samo z routingiem, więc potrzebowałem tego! Dzięki!
Trevor de Koekkoek
Dzięki, próbowałem wymyślić, jak zrobić coś podobnego.
Szansa
dał mi pomysł na własną realizację, wielkie dzięki!
Alexander Beletsky
3
pamiętaj, aby ustawić area = null (lub właściwy obszar), jeśli używasz MVC 2 i nowszych - w przeciwnym razie zostanie on odziedziczony ze strony, którą próbujesz odwiedzić
Simon_Weaver
Czy można to zrobić bez MVC?
DARKGuy

Odpowiedzi:

30

Myślę, że głównym problemem jest to, że jeśli zamierzasz korzystać z wbudowanej klasy ASP.NET FormsAuthentication (i nie ma powodu, aby tego nie robić), coś pod koniec dnia zadzwoni, FormsAuthentication.RedirectToLoginPage()co będzie aby spojrzeć na jeden skonfigurowany adres URL. Zawsze jest tylko jeden adres URL logowania i właśnie tak go zaprojektowali.

Mój problem (prawdopodobnie implementacja Rube Goldberga) polegałby na przekierowaniu do jednej strony logowania w katalogu głównym, udostępnianej wszystkim klientom, powiedzmy / account / login. Ta strona logowania w rzeczywistości niczego nie wyświetla; sprawdza parametr ReturnUrl lub jakąś wartość, którą mam w sesji lub plik cookie, który identyfikuje klienta i używa go do natychmiastowego przekierowania 302 do określonej strony / klient / konto / login. Jest to dodatkowe przekierowanie, ale prawdopodobnie niezauważalne i pozwala korzystać z wbudowanych mechanizmów przekierowania.

Inną opcją jest utworzenie własnego atrybutu niestandardowego zgodnie z opisem i unikanie wszystkiego, co wywołuje RedirectToLoginPage()metodę w FormsAuthenticationklasie, ponieważ zastąpisz go własną logiką przekierowania. (Możesz stworzyć swoją własną klasę, która jest podobna.) Ponieważ jest to klasa statyczna, nie znam żadnego mechanizmu, za pomocą którego mógłbyś po prostu wstrzyknąć swój własny alternatywny interfejs i sprawić, by magicznie działał z istniejącym atrybutem [Authorize], który ciosy, ale ludzie robili już wcześniej podobne rzeczy .

Mam nadzieję, że to pomoże!

Mikołaja Piaseckiego
źródło
jest to prawdopodobnie najbezpieczniejsze podejście. tworzenie własnego atrybutu [MyAuthorize] jest niebezpieczne. chyba, że ​​twoja kompilacja sprawdza, czy ludzie nie używają wbudowanego atrybutu [Authorize], ryzykujesz, że ludzie (lub Ty) zapomną i
użyją
W niektórych przypadkach może być jednak pomocne zastąpienie Application_AuthenticateRequest(zobacz moją odpowiedź poniżej).
turdus-merula
41

W wersji RTM programu ASP.NET MVC brakuje właściwości Cancel. Ten kod działa z 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 }
                        });
            }
        }
    }
}

Edycja: Możesz chcieć wyłączyć domyślny loginUrl uwierzytelniania formularzy w web.config - na wypadek, gdyby ktoś zapomniał, że masz niestandardowy atrybut i przez pomyłkę użyje wbudowanego atrybutu [Authorize].

Zmodyfikuj wartość w web.config:

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

Następnie utwórz metodę akcji „ERROR”, która rejestruje błąd i przekierowuje użytkownika do najbardziej ogólnej strony logowania, jaką masz.

user134936
źródło
2
pamiętaj, aby dodać {area, null} do słownika (lub jakkolwiek nazywa się twój obszar), jeśli używasz MVC 2 i nowszych - w przeciwnym razie zostanie on odziedziczony ze strony, którą próbowałeś odwiedzić
Simon_Weaver,
2

Moim rozwiązaniem tego problemu była niestandardowa ActionResultklasa:

    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
źródło
0

Mimo to, jeśli zdecydujesz się użyć wbudowanego ASP.NET FormsAuthentication, możesz pominąć Application_AuthenticateRequestw Global.asax.csnastępujący sposób:

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
źródło