ASP.NET-Web-API: Richtige Methode zum Zurückgeben einer 401 / nicht autorisierten Antwort

98

Ich habe eine MVC-Webapi-Site, die die OAuth / Token-Authentifizierung zur Authentifizierung von Anforderungen verwendet. Alle relevanten Controller haben die richtigen Attribute und die Authentifizierung funktioniert einwandfrei.

Das Problem ist, dass nicht die gesamte Anforderung im Rahmen eines Attributs autorisiert werden kann - einige Autorisierungsprüfungen müssen in Code durchgeführt werden, der von Controller-Methoden aufgerufen wird - wie kann in diesem Fall eine nicht autorisierte 401-Antwort korrekt zurückgegeben werden?

Ich habe es versucht throw new HttpException(401, "Unauthorized access");, aber wenn ich das mache, ist der Antwortstatuscode 500 und ich bekomme auch einen Stack-Trace. Selbst in unserem Protokollierungs-DelegatingHandler können wir sehen, dass die Antwort 500 und nicht 401 ist.

GoatInTheMachine
quelle
1
Jedem, der diese Antwort auf der ganzen Linie aufgreift, würde ich empfehlen, über den geeigneten Zeitpunkt nachzudenken, um einen HttpResponseExceptionVersus zu werfen, wann er einen zurückgeben soll Unauthorized(). Die Verwendung der Ausnahme für einen 'erwarteten' Fehler ist ein Anti-Muster. Wenn Sie also in einigen Fällen erwarten, dass der Anruf diesen Fehler macht, Unauthorized()ist die Rückgabe wahrscheinlich der richtige Anruf. Speichern Sie HttpResponseExceptionfür das wirklich Unerwartete.
Rikki
@Rikki, 401 ist kein "erwarteter" Fehler. - Es ist ein außergewöhnlicher Umstand, der dazu führen sollte, dass Sie Ihren Workflow abbrechen (außer vielleicht für die Protokollierung, die Sie ausnahmsweise bereits durchführen sollten ...). - Wie auch immer, wenn Sie ein stark typisiertes Ergebnis von Ihrem Controller zurückgeben möchten ( zB zur Erleichterung des Unit-Tests) ist eine Ausnahme eindeutig der beste Weg.
BrainSlugs83

Antworten:

144

Sie sollten eine HttpResponseExceptionaus Ihrer API-Methode werfen , nicht HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Oder wenn Sie eine benutzerdefinierte Nachricht bereitstellen möchten:

var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Oops!!!" };
throw new HttpResponseException(msg);
LukeH
quelle
95

Geben Sie einfach Folgendes zurück:

return Unauthorized();
JohnWrensby
quelle
2
Ich denke, das Akzeptierte beantwortet speziell die Frage des OP. Meine Antwort beantwortet den Titel der Frage "ASP.NET Web API: Richtiger Weg, um eine 401 / nicht autorisierte Antwort zurückzugeben"
JohnWrensby
3
Weiß jemand, warum es keine überladene Version davon mit einer Nachricht gibt?
Simon_Weaver
5
@Simon_Weaver Keine Ahnung warum, aber Sie könnten ein verwenden return Content<string>(HttpStatusCode.Unauthorized, "Message");, um dies zu tun.
Rikki
2
Dies sollte die richtige Antwort sein. 1 es ist richtig. 2) Wenn sich dies in einem späteren Framework ändert, müssen Sie den Code nicht ändern. 3) Sie müssen einem 401 keinen Grund angeben. Dies sollte vom Client und nicht vom Server erledigt werden.
Nick Turner
1
In welcher Bibliothek befindet sich das?
Nae
19

Alternativ zu den anderen Antworten können Sie diesen Code auch verwenden, wenn Sie einen IActionResultinnerhalb eines ASP.NET-Controllers zurückgeben möchten .

ASP.NET

 return Content(HttpStatusCode.Unauthorized, "My error message");

Update: ASP.NET Core

Der obige Code funktioniert in ASP.NET Core nicht. Sie können stattdessen einen der folgenden Codes verwenden:

 return StatusCode((int)System.Net.HttpStatusCode.Unauthorized, "My error message");
 return StatusCode(401, "My error message");

Anscheinend ist die Grundphrase ziemlich optional ( Kann eine HTTP-Antwort die Grundphrase weglassen? )

Alex AIT
quelle
1
Dies funktioniert nicht mehr in ASP.NET Core. Die ControllerBaseKlasse (von ASP.NET Core WebAPI verwendet) weist keine ContentÜberladung mehr auf, die einen HTTP-Statuscode akzeptiert.
Dai
Das ist falsch. Eine Inhaltsantwort hat den Status 200 Ok. Der Server sollte eine 401 senden und der Client sollte entsprechend behandeln. Sie können keine 200 als 401 senden. Das macht keinen Sinn. Wenn der Kunde einen 401 bekommt, ist es kein Ups, es ist ein Verstoß gegen das Gesetz.
Nick Turner
Dieser Code sendet einen 401-Statuscode ( HttpStatusCode.Unauthorized), nicht 200. Content(...)lediglich eine Abkürzung für die Rückgabe eines bestimmten Inhalts mit einem bestimmten HTTP-Statuscode. Wenn Sie 200 senden möchten, können Sie verwendenOk(...)
Alex AIT
@NickTurner - das ist ein Argument dafür, dass die webapi2 Content () -Methode schlecht benannt ist, nicht weil dies die falsche Antwort ist. Da die Methode (Status, Nachricht) in NetCore umbenannt wurde, sind sich die Entwickler wohl einig, dass sie schlecht benannt wurde.
Chris F Carroll
9

Sie erhalten einen 500-Antwortcode, weil Sie eine Ausnahme (die HttpException) auslösen, die auf einen Serverfehler hinweist. Dies ist der falsche Ansatz.

Stellen Sie einfach den Antwortstatuscode .eg ein

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
quelle
Es ist ein bisschen seltsam, dass die Ausnahme den HTTP-Statuscode als Parameter verwendet, und Intellisense-Dokumente sagen, dass dies der an den Client gesendete Statuscode ist. Ich hatte gehofft, die Antwort nicht direkt selbst zu mutieren, da dies fehleranfällig erscheint sein globaler Zustand
GoatInTheMachine
1
Der Basis-Web-API-Controller macht keine ResponseEigenschaft verfügbar .
LukeH
3

Sie können eine vorhandene Antwort in ASP.NET Core> = 1.0 ergänzen

return Unauthorized();

return Unauthorized(object value);

Um Informationen an den Kunden weiterzuleiten, können Sie einen Anruf wie folgt tätigen:

return Unauthorized(new { Ok = false, Code = Constants.INVALID_CREDENTIALS, ...});

Auf dem Client haben Sie neben der 401-Antwort auch die übergebenen Daten. Zum Beispiel auf den meisten Kunden können Sie await response.json()es bekommen.

Gabriel P.
quelle
3

In .Net Core können Sie verwenden

return new ForbidResult();

anstatt

return Unauthorized();

Dies hat den Vorteil, dass auf die nicht autorisierte Standardseite (Account / AccessDenied) umgeleitet wird, anstatt eine gerade 401 zu geben

Um den Standardspeicherort zu ändern, ändern Sie Ihre startup.cs

services.AddAuthentication(options =>...)
            .AddOpenIdConnect(options =>...)
            .AddCookie(options =>
            {
                options.AccessDeniedPath = "/path/unauthorized";

            })
Mattbloke
quelle
Die Frage bezieht sich auf eine Web-API. Das wäre also eine ungültige Antwort, wenn ich nicht falsch liege? Die API sollte keine 'Aktionen' zurückgeben, sondern nur Ergebnisse.
Niels Lucas
1

Sie können den folgenden Code in asp.net core 2.0 verwenden:

public IActionResult index()
{
     return new ContentResult() { Content = "My error message", StatusCode = (int)HttpStatusCode.Unauthorized };
}
AminRostami
quelle
1

Sie folgen auch diesem Code:

var response = new HttpResponseMessage(HttpStatusCode.NotFound)
{
      Content = new StringContent("Users doesn't exist", System.Text.Encoding.UTF8, "text/plain"),
      StatusCode = HttpStatusCode.NotFound
 }
 throw new HttpResponseException(response);
Kamrul Hasan
quelle
Sie müssen den StatusCode nicht erneut festlegen, wenn Sie ihn an den Konstruktor übergeben - beides ist in Ordnung
Jon Story