API Web ASP.NET: modo corretto per restituire una risposta 401 / non autorizzata


99

Ho un sito webapi MVC che utilizza l'autenticazione OAuth / token per autenticare le richieste. Tutti i controller rilevanti hanno gli attributi giusti e l'autenticazione funziona correttamente.

Il problema è che non tutte le richieste possono essere autorizzate nell'ambito di un attributo - alcuni controlli di autorizzazione devono essere eseguiti nel codice chiamato dai metodi del controller - qual è il modo corretto per restituire una risposta 401 non autorizzata in questo caso?

Ho provato throw new HttpException(401, "Unauthorized access");, ma quando lo faccio il codice di stato della risposta è 500 e ottengo anche una traccia dello stack. Anche nella nostra registrazione DelegatingHandler possiamo vedere che la risposta è 500, non 401.


1
A chiunque raccolga questa risposta lungo la linea, suggerirei di pensare al momento appropriato per lanciare un HttpResponseExceptioncontro quando restituire un Unauthorized(). Usare l'eccezione per un errore "atteso" è un po 'un anti-pattern, quindi se ci sono casi in cui ti aspetti che la chiamata commetta questo errore, la restituzione Unauthorized()è probabilmente la chiamata giusta. Tranne HttpResponseExceptionper ciò che è veramente inaspettato.
Rikki

Vedi github.com/aspnet/Mvc/issues/5507 per alcune discussioni.
Rikki

@ Rikki, 401 non è un errore "previsto". - È una circostanza eccezionale che dovrebbe indurti ad interrompere il tuo flusso di lavoro (tranne forse per la registrazione, che dovresti già fare per qualsiasi eccezione ...) - Ad ogni modo, se vuoi restituire un risultato tipizzato forte dal tuo controller ( es. per facilità di test unitario), un'eccezione è chiaramente il percorso migliore.
BrainSlugs83

Risposte:


145

Dovresti lanciare un HttpResponseExceptiondal tuo metodo API, non HttpException:

throw new HttpResponseException(HttpStatusCode.Unauthorized);

Oppure, se desideri fornire un messaggio personalizzato:

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

95

Basta restituire quanto segue:

return Unauthorized();

2
Penso che l'accettato risponda in modo specifico alla domanda del PO. La mia risposta risponde al titolo della domanda "API Web ASP.NET: modo corretto per restituire una risposta 401 / non autorizzata"
JohnWrensby

3
Qualcuno sa perché non esiste una versione sovraccarica di questo con un messaggio?
Simon_Weaver

5
@Simon_Weaver Non ho idea del perché, ma potresti usare a return Content<string>(HttpStatusCode.Unauthorized, "Message");per farlo.
Rikki

2
Questa dovrebbe essere la risposta corretta. 1 è corretto. 2) Se questo cambia in un framework successivo, non è necessario modificare il codice. 3) Non è necessario fornire un motivo a un 401. Questo dovrebbe essere gestito dal client e non dal server.
Nick Turner

1
In quale libreria si trova?
Nae

19

In alternativa alle altre risposte, puoi anche utilizzare questo codice se vuoi restituire un IActionResultall'interno di un controller ASP.NET.

ASP.NET

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

Aggiornamento: ASP.NET Core

Il codice sopra non funziona in ASP.NET Core, puoi invece usare uno di questi:

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

Apparentemente la frase del motivo è piuttosto facoltativa ( una risposta HTTP può omettere la frase-motivo? )


1
Questo non funziona più in ASP.NET Core, la ControllerBaseclasse (usata da ASP.NET Core WebAPI) non ha più un Contentoverload che accetta un codice di stato HTTP.
Dai

Questo è sbagliato. Una risposta di contenuto è uno stato 200 OK. Il server dovrebbe inviare un 401 e il client dovrebbe gestirlo di conseguenza. Non puoi inviare un 200 come 401. Non ha senso. Se il cliente ottiene un 401, non è un Oops, è un tuo infrangere la legge.
Nick Turner

Questo codice invia un codice di stato 401 ( HttpStatusCode.Unauthorized), non 200. Content(...)semplicemente una scorciatoia per restituire un determinato contenuto con un determinato codice di stato HTTP. Se vuoi inviare 200 puoi usareOk(...)
Alex AIT

@NickTurner - questo è un argomento per cui il metodo webapi2 Content () è mal nominato non perché questa è la risposta sbagliata. Poiché il metodo (status, message) viene rinominato in NetCore, immagino che gli sviluppatori concordino sul fatto che sia stato chiamato male.
Chris F Carroll

9

Ottieni un codice di risposta 500 perché stai lanciando un'eccezione (il HttpException) che indica un qualche tipo di errore del server, questo è l'approccio sbagliato.

Basta impostare il codice di stato della risposta .eg

Response.StatusCode = (int)HttpStatusCode.Unauthorized;

È un po 'strano quindi che l'eccezione prenda il codice di stato HTTP come parametro, e i documenti intellisense dicono che questo è il codice di stato inviato al client - speravo di evitare di modificare la risposta da solo direttamente poiché sembra soggetto a errori, visto che il suo stato globale
GoatInTheMachine

1
Il controller API Web di base non espone una Responseproprietà.
LukeH

3

Per aggiungere a una risposta esistente in ASP.NET Core> = 1.0 puoi

return Unauthorized();

return Unauthorized(object value);

Per passare informazioni al cliente puoi fare una chiamata come questa:

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

Sul client oltre alla risposta 401 avrai anche i dati passati. Ad esempio, sulla maggior parte dei clienti puoi await response.json()ottenerlo.


3

In .Net Core È possibile utilizzare

return new ForbidResult();

invece di

return Unauthorized();

che ha il vantaggio di reindirizzare alla pagina non autorizzata predefinita (Account / AccessDenied) piuttosto che dare un semplice 401

per cambiare la posizione predefinita, modificare il file startup.cs

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

            })

La domanda riguarda un'API web. Quindi questa sarebbe una risposta non valida se non sbaglio? L'API non deve restituire "azioni", ma solo risultati.
Niels Lucas

1

puoi usare il codice seguente in asp.net core 2.0:

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

1

Segui anche questo codice:

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

Non è necessario impostare di nuovo lo StatusCode se lo si passa al costruttore - l'utilizzo di uno dei due va bene
Jon Story
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.