Interfejs API sieci Web ASP.NET: poprawny sposób zwracania 401 / nieautoryzowanej odpowiedzi

99

Mam witrynę internetową MVC, która używa uwierzytelniania OAuth / token do uwierzytelniania żądań. Wszystkie odpowiednie kontrolery mają odpowiednie atrybuty, a uwierzytelnianie działa prawidłowo.

Problem polega na tym, że nie wszystkie żądania można autoryzować w zakresie atrybutu - niektóre sprawdzenia autoryzacji muszą być przeprowadzane w kodzie wywoływanym przez metody kontrolera - jaki jest prawidłowy sposób zwrócenia nieautoryzowanej odpowiedzi 401 w tym przypadku?

Próbowałem throw new HttpException(401, "Unauthorized access");, ale kiedy to zrobię, kod statusu odpowiedzi to 500 i otrzymuję również ślad stosu. Nawet w naszym logowaniu DelegatingHandler widzimy, że odpowiedź to 500, a nie 401.

GoatInTheMachine
źródło
1
Każdemu, kto podchwyci tę odpowiedź, proponuję zastanowić się nad odpowiednim momentem, aby rzucić a HttpResponseExceptionversus, kiedy zwrócić Unauthorized(). Używanie wyjątku dla „oczekiwanego” błędu jest trochę anty-wzorzec, więc jeśli są przypadki, w których spodziewasz się, że wywołanie popełni ten błąd, powrót Unauthorized()jest prawdopodobnie właściwym wywołaniem. Oszczędzaj HttpResponseExceptionna naprawdę nieoczekiwane.
Rikki
Więcej dyskusji znajdziesz na github.com/aspnet/Mvc/issues/5507 .
Rikki
@Rikki, 401 nie jest „oczekiwanym” błędem. - To wyjątkowa okoliczność, która powinna spowodować przerwanie przepływu pracy (może z wyjątkiem logowania, które powinieneś już robić w przypadku każdego wyjątku ...) - W każdym razie, jeśli chcesz zwrócić silnie wpisany wynik z kontrolera ( np. w celu ułatwienia testowania jednostkowego), wyjątek jest zdecydowanie najlepszą drogą.
BrainSlugs83

Odpowiedzi:

145

Powinieneś wyrzucać HttpResponseExceptionze swojej metody API, a nie HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Lub, jeśli chcesz podać niestandardową wiadomość:

var msg = new HttpResponseMessage(HttpStatusCode.Unauthorized) { ReasonPhrase = "Oops!!!" };
throw new HttpResponseException(msg);
LukeH
źródło
96

Po prostu zwróć następujące informacje:

return Unauthorized();
JohnWrensby
źródło
2
Myślę, że zaakceptowana odpowiedź konkretnie na pytanie PO. Moja odpowiedź odpowiada tytułowi pytania „ASP.NET Web API: poprawny sposób zwrócenia 401 / nieautoryzowanej odpowiedzi”
JohnWrensby,
3
Czy ktoś wie, dlaczego nie ma przeciążonej wersji tego z komunikatem?
Simon_Weaver,
5
@Simon_Weaver Nie mam pojęcia dlaczego, ale możesz użyć return Content<string>(HttpStatusCode.Unauthorized, "Message");do tego.
Rikki
2
To powinna być prawidłowa odpowiedź. 1 to jest poprawne. 2) Jeśli to się zmieni w późniejszym frameworku, nie musisz zmieniać kodu. 3) Nie musisz podawać powodu 401. Powinien to zrobić klient, a nie serwer.
Nick Turner
1
W której bibliotece to jest?
Nae
19

Alternatywnie do innych odpowiedzi możesz również użyć tego kodu, jeśli chcesz zwrócić IActionResultw kontrolerze ASP.NET.

ASP.NET

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

Aktualizacja: ASP.NET Core

Powyższy kod nie działa w ASP.NET Core, możesz zamiast tego użyć jednego z nich:

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

Najwyraźniej fraza powodu jest dość opcjonalna ( czy w odpowiedzi HTTP można pominąć wyrażenie powodu? )

Alex AIT
źródło
1
To już nie działa w ASP.NET Core, ControllerBaseklasa (używana przez ASP.NET Core WebAPI) nie ma już Contentprzeciążenia, które akceptuje kod stanu HTTP.
Dai,
To jest źle. Odpowiedź zawartości ma status 200 OK. Serwer powinien wysłać 401, a klient powinien to zrobić. Nie możesz wysłać 200 jako 401. To nie ma sensu. Jeśli klient otrzyma 401, to nie jest Ups, to twoje złamanie prawa.
Nick Turner
Ten kod wysyła kod statusu 401 ( HttpStatusCode.Unauthorized), a nie 200. Content(...)po prostu skrót do zwracania dowolnej treści z podanym kodem statusu HTTP. Jeśli chcesz wysłać 200, możesz użyćOk(...)
Alex AIT
@NickTurner - to argument przemawiający za niewłaściwą nazwą metody webapi2 Content (), a nie dlatego, że jest to zła odpowiedź. Ponieważ nazwa metody (status, wiadomość) została zmieniona w NetCore, myślę, że twórcy zgadzają się, że została źle nazwana.
Chris F Carroll
9

Otrzymujesz kod odpowiedzi 500, ponieważ rzucasz wyjątek (the HttpException), który wskazuje na jakiś rodzaj błędu serwera, to jest niewłaściwe podejście.

Wystarczy ustawić kod statusu odpowiedzi .eg

Response.StatusCode = (int)HttpStatusCode.Unauthorized;
DGibbs
źródło
To trochę dziwne, że wyjątek przyjmuje kod stanu HTTP jako parametr, a dokumentacja Intellisense twierdzi, że jest to kod stanu wysłany do klienta - miałem nadzieję, że uniknę bezpośredniej mutacji odpowiedzi, ponieważ wydaje się to podatne na błędy, widząc, jak jej stan globalny
GoatInTheMachine
1
Podstawowy kontroler interfejsu API sieci Web nie uwidacznia Responsewłaściwości.
LukeH
3

Aby dodać do istniejącej odpowiedzi w ASP.NET Core> = 1,0, możesz

return Unauthorized();

return Unauthorized(object value);

Aby przekazać informacje klientowi, możesz zadzwonić w ten sposób:

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

Na kliencie oprócz odpowiedzi 401 będziesz miał również przekazane dane. Na przykład w przypadku większości klientów możeszawait response.json() to uzyskać.

Gabriel P.
źródło
3

W .Net Core możesz użyć

return new ForbidResult();

zamiast

return Unauthorized();

który ma tę zaletę, że przekierowuje na domyślną nieautoryzowaną stronę (Odmowa dostępu / Konta) zamiast podawania prostego 401

aby zmienić domyślną lokalizację, zmodyfikuj plik startup.cs

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

            })
mattbloke
źródło
Pytanie dotyczy internetowego interfejsu API. Więc to byłaby nieprawidłowa odpowiedź, jeśli się nie mylę? API nie powinno zwracać „działań”, tylko wyniki.
Niels Lucas
1

możesz użyć następującego kodu w asp.net core 2.0:

public IActionResult index()
{
     return new ContentResult() { Content = "My error message", StatusCode = (int)HttpStatusCode.Unauthorized };
}
AminRostami
źródło
1

Postępujesz również zgodnie z tym kodem:

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
źródło
Nie musisz ponownie ustawiać StatusCode, jeśli przekażesz go konstruktorowi - użycie któregokolwiek z nich jest w porządku
Jon Story