c# not - Nicht autorisierter Webapi-Aufruf, der die Anmeldeseite statt 401 zurückgibt




unauthorized net (12)

Wie konfiguriere ich mein mvc / webapi-Projekt so, dass eine Webapi-Methode, die aus einer Rasiereransicht aufgerufen wird, die Anmeldeseite nicht zurückgibt, wenn sie nicht autorisiert ist?

Es ist eine MVC5-Anwendung, die auch WebApi-Controller für Aufrufe über Javascript hat.

Die zwei folgenden Methoden

[Route("api/home/LatestProblems")]      
[HttpGet()]
public List<vmLatestProblems> LatestProblems()
{
    // Something here
}

[Route("api/home/myLatestProblems")]
[HttpGet()]
[Authorize(Roles = "Member")]
public List<vmLatestProblems> mylatestproblems()
{
   // Something there
}

werden über den folgenden Winkelcode aufgerufen:

angular.module('appWorship').controller('latest', 
    ['$scope', '$http', function ($scope,$http) {         
        var urlBase = baseurl + '/api/home/LatestProblems';
        $http.get(urlBase).success(function (data) {
            $scope.data = data;
        }).error(function (data) {
            console.log(data);
        });
        $http.get(baseurl + '/api/home/mylatestproblems')
          .success(function (data) {
            $scope.data2 = data;
        }).error(function (data) {
            console.log(data);
        });  
    }]
);

Also bin ich nicht eingeloggt und die erste Methode liefert erfolgreich Daten zurück. Die zweite Methode gibt (in der Erfolgsfunktion) Daten zurück, die das Äquivalent einer Anmeldeseite enthalten. zB was Sie in mvc bekommen würden, wenn Sie eine Controller-Aktion angefordert hätten, die mit [Authorize] abgestempelt wurde und Sie nicht eingeloggt waren.

Ich möchte, dass ein 401 unautorisiert zurückgegeben wird, damit ich unterschiedliche Daten für Benutzer anzeigen kann, basierend darauf, ob sie eingeloggt sind oder nicht. Im Idealfall, wenn der Benutzer angemeldet ist, möchte ich auf die Benutzereigenschaft des Controllers zugreifen können, damit ich spezifische Daten für dieses Mitglied zurückgeben kann.

UPDATE: Da keiner der folgenden Vorschläge mehr zu funktionieren scheint (Änderungen an Identity oder WebAPI), habe ich ein github auf github das das Problem verdeutlichen sollte.


Answers

Nach vielen Versuchen, die Umleitungen auf die Anmeldeseite zu vermeiden, wurde mir klar, dass dies für das Autorize-Attribut eigentlich angemessen ist. Es heißt, geh und hol dir Autorisierung. Anstatt für Api-Anrufe, die nicht autorisiert sind, wollte ich nur keine Informationen an Hacker weitergeben. Dieses Ziel war leichter zu erreichen, indem ein neues Attribut von Authorize hinzugefügt wurde, das den Inhalt stattdessen als Fehler 404 ausblendet:

public class HideFromAnonymousUsersAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
         actionContext.Response = ActionContext.Request.CreateErrorResponse(HttpStatusCode.NotFound, "Access Restricted");
    }
}

Es fiel mir schwer, sowohl den Statuscode als auch eine Textantwort in den OnAuthorization / HandleUnauthorizedRequest-Methoden zu erhalten. Dies erwies sich als die beste Lösung für mich:

    actionContext.Response = new HttpResponseMessage()
    {
        StatusCode = HttpStatusCode.Forbidden,
        Content = new StringContent(unauthorizedMessage)
    };

In MVC 5 mit Dot Net Framework 4.5.2 bekommen wir "application / json, plaint text .." unter "Accept" header Es wird schön sein wie folgt zu verwenden:

isJson = headers["Content-Type"] == "application/json" || headers["Accept"].IndexOf("application/json", System.StringComparison.CurrentCultureIgnoreCase) >= 0;

Wenn Sie asp.net WebApi in asp.net MVC Website hinzufügen möchten Sie wahrscheinlich auf einige Anfragen nicht autorisiert reagieren. Aber dann kommt die ASP.NET-Infrastruktur ins Spiel und wenn Sie versuchen, den Antwortstatus-Code auf HttpStatusCode.Unauthorized zu setzen, erhalten Sie 302 Redirect zur Login-Seite.

Wenn Sie asp.net identity und owin-based authentication hier einen Code verwenden, der zur Lösung dieses Problems beitragen kann:

public void ConfigureAuth(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/Account/Login"),
        Provider = new CookieAuthenticationProvider()
        {
            OnApplyRedirect = ctx =>
            {
                if (!IsApiRequest(ctx.Request))
                {
                    ctx.Response.Redirect(ctx.RedirectUri);
                }
            }
        }
    });

    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}


private static bool IsApiRequest(IOwinRequest request)
{
    string apiPath = VirtualPathUtility.ToAbsolute("~/api/");
    return request.Uri.LocalPath.StartsWith(apiPath);
}

Ich habe die gleiche Situation, wenn OWIN die 401-Antwort immer auf die Login-Seite von WebApi umleitet. Unsere Web-API unterstützt nicht nur Ajax-Anrufe von Angular-, sondern auch Mobile-, Win-Form-Anrufen. Daher ist die Lösung, um zu überprüfen, ob die Anfrage eine Ajax-Anfrage ist, für unseren Fall nicht wirklich sortiert.

Ich habe einen anderen Ansatz gewählt, um neue Header-Antwort zu injizieren: Suppress-Redirect wenn Antworten von WebApi kommen. Die Implementierung ist auf Handler:

public class SuppressRedirectHandler : DelegatingHandler
{
    /// <summary>
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith(task =>
        {
            var response = task.Result;
            response.Headers.Add("Suppress-Redirect", "True");
            return response;
        }, cancellationToken);
    }
}

Und registrieren Sie diesen Handler in der globalen Ebene von WebApi:

config.MessageHandlers.Add(new SuppressRedirectHandler());

So können Sie beim Start von OWIN prüfen, ob der Response-Header Suppress-Redirect :

public void Configuration(IAppBuilder app)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationMode = AuthenticationMode.Active,
        AuthenticationType = DefaultApplicationTypes.ApplicationCookie,
        ExpireTimeSpan = TimeSpan.FromMinutes(48),

        LoginPath = new PathString("/NewAccount/LogOn"),

        Provider = new CookieAuthenticationProvider()
        {
            OnApplyRedirect = ctx =>
            {
                var response = ctx.Response;
                if (!IsApiResponse(ctx.Response))
                {
                    response.Redirect(ctx.RedirectUri);
                }
            }
        }
    });
}

private static bool IsApiResponse(IOwinResponse response)
{
    var responseHeader = response.Headers;

    if (responseHeader == null) 
        return false;

    if (!responseHeader.ContainsKey("Suppress-Redirect"))
        return false;

    if (!bool.TryParse(responseHeader["Suppress-Redirect"], out bool suppressRedirect))
        return false;

    return suppressRedirect;
}

Es gibt zwei AuthorizeAttribute-Implementierungen, und Sie müssen sicherstellen, dass Sie auf das richtige für Web-APIs verweisen. Es gibt System.Web.Http.AuthorizeAttribute das für Web-APIs verwendet wird, und System.Web.Mvc.AuthorizeAttribute das für Controller mit Sichten verwendet wird. Http.AuthorizeAttribute gibt einen 401-Fehler zurück, wenn die Autorisierung fehlschlägt und Mvc.AuthorizeAttribute auf die Anmeldeseite umleitet.

Aktualisiert 26.11.2013

Es scheint also, dass sich die Dinge mit MVC 5 drastisch geändert haben, wie Brock Allen in seinem Artikel betonte. Ich nehme an, die OWIN-Pipeline übernimmt und führt ein neues Verhalten ein. Jetzt, wenn der Benutzer nicht autorisiert ist, wird ein Status von 200 mit den folgenden Informationen in dem HTTP-Header zurückgegeben.

X-Responded-JSON: {"status":401,"headers":{"location":"http:\/\/localhost:59540\/Account\/Login?ReturnUrl=%2Fapi%2FTestBasic"}}

Sie könnten Ihre Logik auf der Clientseite ändern, um diese Informationen in der Kopfzeile zu überprüfen, um zu bestimmen, wie Sie damit umgehen, anstatt nach einem 401-Status in der Fehlerverzweigung zu suchen.

Ich habe versucht, dieses Verhalten in einem benutzerdefinierten AuthorizeAttribute zu überschreiben, indem ich den Status in der Antwort in den Methoden OnAuthorization und HandleUnauthorizedRequest festlegte .

actionContext.Response = new HttpResponseMessage(System.Net.HttpStatusCode.Unauthorized);

Aber das hat nicht funktioniert. Die neue Pipeline muss diese Antwort später abrufen und sie an die gleiche Antwort anpassen, die ich zuvor erhalten habe. Das Auslösen einer HttpException funktionierte auch nicht, da es nur in einen Fehlerstatus von 500 geändert wurde.

Ich habe die Lösung von Brock Allen getestet und es funktionierte, als ich einen jQuery Ajax Call benutzte. Wenn es für Sie nicht funktioniert, ist es wahrscheinlich, dass Sie eckig sind. Führen Sie Ihren Test mit Fiddler und sehen, ob das Folgende in Ihrer Kopfzeile ist.

X-Requested-With: XMLHttpRequest

Wenn nicht, dann ist das das Problem. Ich bin nicht vertraut mit eckigen, aber wenn Sie können Sie Ihre eigenen Header-Werte einfügen, dann fügen Sie dies zu Ihren Ajax-Anfragen und es wird wahrscheinlich zu arbeiten.


Ich hatte auch eine MVC5-Anwendung (System.Web) mit WebApi (mit OWIN) und wollte nur verhindern, dass 401 Antworten von WebApi auf 302 Antworten geändert wurden.

Was für mich funktionierte, war, eine angepasste Version des WebApi AuthorizeAttribute wie folgt zu erstellen:

public class MyAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(HttpActionContext actionContext)
    {
        base.HandleUnauthorizedRequest(actionContext);
        HttpContext.Current.Response.SuppressFormsAuthenticationRedirect = true;
    }
}

Und um es anstelle des Standard-WebApi AuthorizeAttribute zu verwenden. Ich habe das Standard MVC AuthorizeAttribute verwendet, um das MVC-Verhalten unverändert zu lassen.


Wenn Sie Ihre Web API über Ihr MVC Projekt MVC , müssen Sie ein benutzerdefiniertes AuthorizeAttribute erstellen, das auf Ihre API Methoden API wird. Innerhalb der IsAuthorized override Sie den aktuellen HttpContext greifen, um die Umleitung zu verhindern, so:

    protected override bool IsAuthorized(HttpActionContext actionContext)
    {
        if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
        {
            // If using Basic Authentication, do that here
        }

        if (Thread.CurrentPrincipal.Identity.Name.Length == 0)
        {
            var response = HttpContext.Current.Response;
            response.SuppressFormsAuthenticationRedirect = true;
            response.StatusCode = (int)System.Net.HttpStatusCode.Forbidden;
            response.End();
        }

        return base.IsAuthorized(actionContext);
    }

Wenn Sie Content-Type == application / json abfangen möchten, können Sie diesen Code verwenden:

private static bool IsAjaxRequest(IOwinRequest request)
    {
        IReadableStringCollection queryXML = request.Query;
        if ((queryXML != null) && (queryXML["X-Requested-With"] == "XMLHttpRequest"))
        {
            return true;
        }

        IReadableStringCollection queryJSON = request.Query;
        if ((queryJSON != null) && (queryJSON["Content-Type"] == "application/json"))
        {
            return true;
        }

        IHeaderDictionary headersXML = request.Headers;
        var isAjax = ((headersXML != null) && (headersXML["X-Requested-With"] == "XMLHttpRequest"));

        IHeaderDictionary headers = request.Headers;
        var isJson = ((headers != null) && (headers["Content-Type"] == "application/json"));

        return isAjax || isJson;

    }

Grüße!!


Installieren Sie einfach das folgende NeGet-Paket

Installieren Sie das Paket Microsoft.AspNet.WebApi.Owin

Schreiben Sie den folgenden Code in die WebApiConfig-Datei.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        //Web API configuration and services
        //Configure Web API to use only bearer token authentication.
        config.SuppressDefaultHostAuthentication();
        config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{action}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
        config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("multipart/form-data"));
    }
}

In früheren Versionen von ASP.NET mussten Sie eine ganze Menge Dinge tun , um das zu erreichen.

Die gute Nachricht ist, dass Sie ASP.NET 4.5 verwenden. Sie können die Formularauthentifizierung mit der neuen HttpResponse.SuppressFormsAuthenticationRedirect Eigenschaft deaktivieren.

In Global.asax :

protected void Application_EndRequest(Object sender, EventArgs e)
{
        HttpApplication context = (HttpApplication)sender;
        context.Response.SuppressFormsAuthenticationRedirect = true;
}

Bearbeiten : Vielleicht möchten Sie auch einen Blick auf diesen Artikel von Sergey Zwezdin, die eine raffiniertere Art und Weise zu erreichen, was Sie versuchen zu tun haben.

Relevante Code-Snippets und Autor-Narration unten eingefügt. Ursprünglicher Autor von Code und Erzählung - Sergey Zwezdin .

Zuerst - lassen Sie uns feststellen, ob die aktuelle HTTP-Anfrage eine AJAX-Anfrage ist. Wenn ja, sollten wir das Ersetzen von HTTP 401 durch HTTP 302 deaktivieren:

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        var httpContext = filterContext.HttpContext;
        var request = httpContext.Request;
        var response = httpContext.Response;

        if (request.IsAjaxRequest())
            response.SuppressFormsAuthenticationRedirect = true;

        base.HandleUnauthorizedRequest(filterContext);
    }
}

Zweitens - fügen wir eine Bedingung hinzu: Wenn der Benutzer authentifiziert wurde, senden wir HTTP 403; und HTTP 401 andernfalls.

public class ApplicationAuthorizeAttribute : AuthorizeAttribute
{
    protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
    {
        var httpContext = filterContext.HttpContext;
        var request = httpContext.Request;
        var response = httpContext.Response;
        var user = httpContext.User;

        if (request.IsAjaxRequest())
        {
            if (user.Identity.IsAuthenticated == false)
                response.StatusCode = (int)HttpStatusCode.Unauthorized;
            else
                response.StatusCode = (int)HttpStatusCode.Forbidden;

            response.SuppressFormsAuthenticationRedirect = true;
            response.End();
        }

        base.HandleUnauthorizedRequest(filterContext);
    }
}

Gut gemacht. Jetzt sollten wir alle Benutzungen des Standard AuthorizeAttribute durch diesen neuen Filter ersetzen. Es kann nicht für Simes Jungs, die Ästhet des Codes ist. Aber ich weiß es nicht anders. Wenn Sie haben, lassen Sie uns bitte zu den Kommentaren gehen.

Die letzte, was wir tun sollten - HTTP-401/403-Behandlung auf einer Client-Seite hinzufügen. Wir können ajaxError bei jQuery verwenden, um Code-Duplikation zu vermeiden:

$(document).ajaxError(function (e, xhr) {
    if (xhr.status == 401)
        window.location = "/Account/Login";
    else if (xhr.status == 403)
        alert("You have no enough permissions to request this resource.");
});

Das Ergebnis -

  • Wenn der Benutzer nicht authentifiziert ist, wird er nach jedem AJAX-Anruf auf eine Anmeldeseite weitergeleitet.
  • Wenn der Benutzer authentifiziert ist, aber keine ausreichenden Berechtigungen hat, wird er eine benutzerfreundliche Error-Nachricht sehen.
  • Wenn der Benutzer authentifiziert ist und genügend Berechtigungen hat, gibt es keine Fehler und die HTTP-Anfrage wird wie gewohnt fortgesetzt.

Eine weitere Möglichkeit, einen eigenen NuGet-Server zu hosten, ist die Verwendung von JetBrains TeamCity als Build-Server. Das Setup ist here beschrieben.

Der Team City-Server fungiert als NuGet-Repository, auf das nur in Ihrem Unternehmen zugegriffen werden könnte / sollte.

Basierend auf Ihrer Nutzung gibt es kostenlose Versionen der Software.

Es hat einige nette Optionen, wie die Möglichkeit, eine neue NuGet-Version auf Anfrage zu veröffentlichen, mit jeder neuen fortlaufenden Integration, etc. Eines der nützlichsten Bits (wie bei allen NuGet-Server-Implementierungen) ist, dass es Dutzende von älteren Versionen enthält Wenn Sie ein Projekt haben, das auf die neueste Version verweisen muss, und ein anderes Projekt, das auf eine ältere Version verweisen muss, wird alles funktionieren.





c# asp.net-web-api asp.net-mvc-5 asp.net-identity owin