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


5 Answers

Brock Allen hat einen schönen Blog-Post darüber, wie man 401 für Ajax-Aufrufe zurückgibt, wenn Cookie-Authentifizierung und OWIN verwendet werden. http://brockallen.com/2013/10/27/using-cookie-authentication-middleware-with-web-api-and-401-response-codes/

Fügen Sie dies in der ConfigureAuth-Methode in der Datei Startup.Auth.cs ein:

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

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

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.




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



Wenn ich die Azure Active Directory-Integration selbst nutze, CookieAuthentication der Ansatz mit der CookieAuthentication Middleware für mich nicht. Ich musste folgendes tun:

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ...
        Notifications = new OpenIdConnectAuthenticationNotifications
        {   
            ...         
            RedirectToIdentityProvider = async context =>
            {
                if (!context.Request.Accept.Contains("html"))
                {
                    context.HandleResponse();
                }
            },
            ...
        }
    });

Wenn die Anfrage vom Browser selbst kommt (und nicht etwa von einem AJAX-Aufruf), dann enthält der Accept-Header irgendwo die Zeichenfolge html . Nur wenn der Client HTML annimmt, halte ich eine Weiterleitung für etwas Nützliches.

Meine Client-Anwendung kann den 401 behandeln, der den Benutzer darüber informiert, dass die App keinen Zugriff mehr hat und erneut geladen werden muss, um sich erneut einzuloggen.




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



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.




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



Related