Comment rediriger vers une URL de connexion dynamique dans ASP.NET MVC

96

Je crée un site Web mutualisé qui héberge des pages pour les clients. Le premier segment de l'URL sera une chaîne qui identifie le client, définie dans Global.asax à l'aide du schéma de routage d'URL suivant:

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

Cela fonctionne bien, avec des URL telles que / foo / Home / Index.

Cependant, lorsque j'utilise l'attribut [Authorize], je souhaite rediriger vers une page de connexion qui utilise également le même schéma de mappage. Donc, si le client est foo, la page de connexion serait / foo / Account / Login au lieu de la redirection fixe / Account / Login définie dans web.config.

MVC utilise un HttpUnauthorizedResult pour renvoyer un statut 401 non autorisé, ce qui, je suppose, oblige ASP.NET à rediriger vers la page définie dans web.config.

Alors, est-ce que quelqu'un sait comment remplacer le comportement de redirection de connexion ASP.NET? Ou serait-il préférable de rediriger dans MVC en créant un attribut d'autorisation personnalisé?

EDIT - Réponse: après quelques fouilles dans la source .Net, j'ai décidé qu'un attribut d'authentification personnalisé était la meilleure solution:

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
la source
2
faire presque exactement la même chose avec le routage, donc j'en avais besoin! Merci!
Trevor de Koekkoek
Merci, j'essayais de comprendre comment faire quelque chose de similaire.
Chance
cela m'a donné une idée de sa propre implémentation, merci beaucoup!
Alexander Beletsky
3
assurez-vous de définir area = null (ou à la bonne zone) si vous utilisez MVC 2 et supérieur - sinon il sera hérité de la page que vous avez tenté de visiter
Simon_Weaver
Un moyen de faire cela sans MVC?
DARKGuy

Réponses:

30

Je pense que le principal problème est que si vous allez utiliser la classe ASP.NET FormsAuthentication intégrée (et il n'y a aucune bonne raison que vous ne devriez pas), quelque chose à la fin de la journée va appeler FormsAuthentication.RedirectToLoginPage() qui va pour regarder l'URL configurée. Il n'y a qu'une seule URL de connexion, jamais, et c'est ainsi qu'ils l'ont conçue.

Mon essai au problème (peut-être une implémentation de Rube Goldberg) serait de le laisser rediriger vers une seule page de connexion à la racine partagée par tous les clients, par exemple / compte / login. Cette page de connexion n'afficherait rien; il inspecte le paramètre ReturnUrl ou une valeur que j'ai dans la session ou un cookie qui identifie le client et l'utilise pour émettre une redirection 302 immédiate vers la page spécifique / client / account / login. C'est une redirection supplémentaire, mais probablement pas perceptible et elle vous permet d'utiliser les mécanismes de redirection intégrés.

L'autre option est de créer votre propre attribut personnalisé pendant que vous décrivez et d'éviter tout ce qui appelle la RedirectToLoginPage()méthode sur la FormsAuthenticationclasse, car vous le remplacerez par votre propre logique de redirection. (Vous pouvez créer votre propre classe similaire.) Comme il s'agit d'une classe statique, je ne connais aucun mécanisme par lequel vous pourriez simplement injecter votre propre interface alternative et la faire fonctionner comme par magie avec l'attribut [Authorize] existant, qui coups, mais les gens ont déjà fait des choses similaires .

J'espère que cela pourra aider!

Nicolas Piasecki
la source
c'est probablement l'approche la plus sûre. créer votre propre attribut [MyAuthorize] est dangereux. sauf si votre build vérifie que les gens n'utilisent pas l'attribut intégré [Authorize], vous risquez que les gens (ou vous-même) oublient et utilisent le mauvais
Simon_Weaver
Dans certains cas, il pourrait être utile de passer outre Application_AuthenticateRequest(voir ma réponse ci-dessous).
turdus-merula
41

Dans la version RTM d'ASP.NET MVC, la propriété Cancel est manquante. Ce code fonctionne avec 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: Vous pouvez désactiver l'authentification par formulaire par défaut loginUrl dans web.config - au cas où quelqu'un oublie que vous avez un attribut personnalisé et utilise l'attribut intégré [Authorize] par erreur.

Modifiez la valeur dans web.config:

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

Ensuite, créez une méthode d'action «ERREUR» qui enregistre une erreur et redirige l'utilisateur vers la page de connexion la plus générique que vous avez.

user134936
la source
2
assurez-vous d'ajouter {area, null} au dictionnaire (ou quel que soit le nom de votre région) si vous utilisez MVC 2 et supérieur - sinon il sera hérité de la page que vous avez tenté de visiter
Simon_Weaver
2

Ma solution à ce problème était une ActionResultclasse personnalisée :

    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
la source
0

Pourtant, si l' on décide d'utiliser le ASP.NET FormsAuthentication intégré, on peut overide Application_AuthenticateRequestdans la Global.asax.csmanière suivante:

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
la source