web-dev-qa-db-fra.com

Authentification par formulaire: désactiver la redirection vers la page de connexion

J'ai une application qui utilise l'authentification par formulaire ASP.NET. Pour l’essentiel, cela fonctionne très bien, mais j’essaie d’ajouter un support pour une API simple via un fichier .ashx. Je veux que le fichier ashx ait une authentification facultative (c'est-à-dire si vous ne fournissez pas d'en-tête Authentication, alors cela fonctionne de manière anonyme). Mais, selon ce que vous faites, je veux exiger une authentification sous certaines conditions.

Je pensais qu'il serait simple de répondre avec le code d'état 401 si l'authentification requise n'était pas fournie, mais il semble que le module Forms Authentcation l'intercepte et renvoie une redirection vers la page de connexion. Ce que je veux dire, c'est si ma méthode ProcessRequest ressemble à ceci:

public void ProcessRequest(HttpContext context)
{
    Response.StatusCode = 401;
    Response.StatusDescription = "Authentication required";
}

Ensuite, au lieu d’obtenir un code d’erreur 401 sur le client, comme je l’attendais, je suis en train de obtenir une redirection 302 vers la page de connexion.

Pour le trafic HTTP nornal, je vois à quel point cela pourrait être utile, mais pour ma page API, je souhaite que la 401 soit modifiée sans modification afin que l’appelant côté client puisse y répondre par programme à la place.

Y'a-t'il un quelconque moyen d'y arriver?

64
Dean Harding

ASP.NET 4.5 a ajouté la propriété booléenne HttpResponse.SuppressFormsAuthenticationRedirect .

public void ProcessRequest(HttpContext context)
{
    Response.StatusCode = 401;
    Response.StatusDescription = "Authentication required";
    Response.SuppressFormsAuthenticationRedirect = true;
}
70
zacharydl

Après quelques recherches, il semble que FormsAuthenticationModule ajoute un gestionnaire pour l'événement HttpApplicationContext.EndRequest. Dans son gestionnaire, il recherche un code de statut 401 et effectue plutôt un Response.Redirect(loginUrl) à la place. Autant que je sache, il n’ya aucun moyen de remplacer ce comportement si vous voulez utiliser FormsAuthenticationModule.

J'ai fini par contourner le problème en désactivant la variable FormsAuthenticationModule dans le fichier web.config, comme ceci:

<authentication mode="None" />

Et puis en implémentant le Application_AuthenticateEvent moi-même:

void Application_AuthenticateRequest(object sender, EventArgs e)
{
    if (Context.User == null)
    {
        var oldTicket = ExtractTicketFromCookie(Context, FormsAuthentication.FormsCookieName);
        if (oldTicket != null && !oldTicket.Expired)
        {
            var ticket = oldTicket;
            if (FormsAuthentication.SlidingExpiration)
            {
                ticket = FormsAuthentication.RenewTicketIfOld(oldTicket);
                if (ticket == null)
                    return;
            }

            Context.User = new GenericPrincipal(new FormsIdentity(ticket), new string[0]);
            if (ticket != oldTicket)
            {
                // update the cookie since we've refreshed the ticket
                string cookieValue = FormsAuthentication.Encrypt(ticket);
                var cookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName] ??
                             new HttpCookie(FormsAuthentication.FormsCookieName, cookieValue) { Path = ticket.CookiePath };

                if (ticket.IsPersistent)
                    cookie.Expires = ticket.Expiration;
                cookie.Value = cookieValue;
                cookie.Secure = FormsAuthentication.RequireSSL;
                cookie.HttpOnly = true;
                if (FormsAuthentication.CookieDomain != null)
                    cookie.Domain = FormsAuthentication.CookieDomain;
                Context.Response.Cookies.Remove(cookie.Name);
                Context.Response.Cookies.Add(cookie);
            }
        }
    }
}

private static FormsAuthenticationTicket ExtractTicketFromCookie(HttpContext context, string name)
{
    FormsAuthenticationTicket ticket = null;
    string encryptedTicket = null;

    var cookie = context.Request.Cookies[name];
    if (cookie != null)
    {
        encryptedTicket = cookie.Value;
    }

    if (!string.IsNullOrEmpty(encryptedTicket))
    {
        try
        {
            ticket = FormsAuthentication.Decrypt(encryptedTicket);
        }
        catch
        {
            context.Request.Cookies.Remove(name);
        }

        if (ticket != null && !ticket.Expired)
        {
            return ticket;
        }

        // if the ticket is expired then remove it
        context.Request.Cookies.Remove(name);
        return null;
    }
}

C'est en fait un peu plus compliqué que cela, mais j'ai essentiellement obtenu le code en regardant l'implémentation de FormsAuthenticationModule dans le réflecteur. Mon implémentation est différente de la variable FormsAuthenticationModule intégrée en ce sens qu'elle ne fait rien si vous répondez avec un 401 - aucune redirection vers la page de connexion. J'imagine que si cela devenait nécessaire, je pourrais mettre un élément dans le contexte pour désactiver la redirection automatique.

35
Dean Harding

Je ne sais pas si cela fonctionnera pour tout le monde, mais dans IIS7, vous pouvez appeler Response.End () après avoir défini le code d'état et la description. De cette façon, # & $ ^ # @ *! FormsAuthenticationModule ne fera pas de redirection.

public void ProcessRequest(HttpContext context) {
    Response.StatusCode = 401;
    Response.StatusDescription = "Authentication required";
    Response.End();
}
11
Luke Sampson

Pour compléter légèrement la réponse de zacharydl, je l’ai utilisée pour résoudre mes problèmes. Au début de chaque demande, s'il s'agit d'AJAX, supprimez immédiatement le comportement.

protected void Application_BeginRequest()
{
    HttpRequestBase request = new HttpRequestWrapper(Context.Request);
    if (request.IsAjaxRequest())
    {
        Context.Response.SuppressFormsAuthenticationRedirect = true;
    }
}
7
Tyler Forsythe

Je ne sais pas comment Response.End () a fonctionné pour vous. Je l'ai essayé sans joie, puis j'ai examiné MSDN pour Response.End (): 'arrête l'exécution de la page et déclenche l'événement EndRequest'.

Pour ce que ça vaut mon bidouille était:

_response.StatusCode = 401;
_context.Items["401Override"] = true;
_response.End();

Ensuite, dans Global.cs, ajoutez un gestionnaire EndRequest (appelé après l'authentification HTTPModule):

protected void Application_EndRequest(object sender, EventArgs e)
{
    if (HttpContext.Current.Items["401Override"] != null)
    {
        HttpContext.Current.Response.Clear();
        HttpContext.Current.Response.StatusCode = 401;
    }
}
5
JaiB

Je sais qu'il existe déjà une réponse avec tick, mais en essayant de résoudre un problème similaire, j'ai trouvé this ( http://blog.inedo.com/2010/12/12/http-418-im-a- théière-enfin-a-% e2% 80% 9clegitimate% e2% 80% 9d-use/ ) comme alternative.

En principe, vous renvoyez votre propre code d’état HTTP (par exemple 418) dans votre code. Dans mon cas, un service de données WCF.

throw new DataServiceException(418, "401 Unauthorized");

Utilisez ensuite un module HTTP pour le gérer lors de l'événement EndRequest afin de réécrire le code en 401.

HttpApplication app = (HttpApplication)sender;
if (app.Context.Response.StatusCode == 418)
{
    app.Context.Response.StatusCode = 401;
}

Le navigateur/client recevra le contenu et le code de statut corrects, cela fonctionne très bien pour moi :)

Si vous souhaitez en savoir plus sur le code d'état HTTP 418, voir this question & answer .

5
airmanx86

ce que vous avez découvert est correct à propos des formulaires autorisés à intercepter le 401 et à effectuer une redirection, mais nous pouvons également le faire pour inverser la configuration.

En gros, vous avez besoin d’un module http pour intercepter la redirection 302 vers la page de connexion et l’inverser en 401.

Les étapes pour le faire sont expliquées dans ici

Le lien donné concerne un service WCF, mais il est identique dans tous les scénarios d'authentification de formulaires.

Comme expliqué dans le lien ci-dessus, vous devez également effacer les en-têtes http, mais n'oubliez pas de replacer l'en-tête du cookie dans la réponse si la réponse d'origine (c'est-à-dire avant l'interception) contenait des cookies. 

4
Amila

C'est un problème connu, et il existe un NuGet Package pour cela et/ou le code source disponible.

2
Johnny G. Halife

Vous ne définissez pas l'en-tête WWW-Authenticate dans le code que vous indiquez, le client ne peut donc pas effectuer d'authentification HTTP au lieu de l'authentification par formulaire. Si tel est le cas, vous devriez utiliser 403 au lieu de 401, qui ne sera pas intercepté par le FormsAuthenticaitonModule.

0
Jesper Kristensen

Funny hack si vous utilisez .NET Framework> = v4.0 mais <v4.5. Il utilise reflet pour définir la valeur de la propriété inaccessible SuppressFormsAuthenticationRedirect:

// Set property to "true" using reflection
Response
  .GetType()
  .GetProperty("SuppressFormsAuthenticationRedirect")
  .SetValue(Response, true, null);
0
German Latorre