2015-02-17 9 views
9

Unten ist der Code, der Authentifizierung durchführt, den Authorization-Header generiert und die API aufruft.Autorisierungsheader ist verloren auf Umleitung

Leider bekomme ich einen 401 Unauthorized Fehler nach der GET Anfrage auf der API.

Wenn ich jedoch den Verkehr in Fiddler erfassen und wiederholen, ist der Aufruf der API erfolgreich und ich kann den gewünschten 200 OK Statuscode sehen.

[Test] 
public void RedirectTest() 
{ 
    HttpResponseMessage response; 
    var client = new HttpClient(); 
    using (var authString = new StringContent(@"{username: ""theUser"", password: ""password""}", Encoding.UTF8, "application/json")) 
    { 
     response = client.PostAsync("http://host/api/authenticate", authString).Result; 
    } 

    string result = response.Content.ReadAsStringAsync().Result; 
    var authorization = JsonConvert.DeserializeObject<CustomAutorization>(result); 
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Token); 
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.host+json;version=1"); 

    response = 
     client.GetAsync("http://host/api/getSomething").Result; 
    Assert.True(response.StatusCode == HttpStatusCode.OK); 
} 

Wenn ich diesen Code ausführen, ist der Berechtigungsheader verloren.

In Fiddler wird dieser Header jedoch erfolgreich übergeben.

Irgendeine Idee, was ich falsch mache?

+0

Wann tritt Umleitung? Welchen HTTP-Code verwenden Sie für die Umleitung? – tia

+0

@tia Ich bekomme 307 Temporary Redirect – Vadim

+0

Nicht sicher, ob [das ist von Relevanz] (http://stackoverflow.com/questions/18914076/can-a-http-redirect-instruct-the-client-to-strip- a-specific-header-from-the-requ) – pixelbadger

Antwort

31

Der Grund für dieses Verhalten ist, dass es von Entwurf ist.

Die meisten HTTP-Clients (standardmäßig) entfernen Autorisierungsheader, wenn sie einer Weiterleitung folgen.

Ein Grund ist Sicherheit. Der Client könnte an einen nicht vertrauenswürdigen Drittanbieterserver weitergeleitet werden, an den Sie Ihr Autorisierungstoken nicht weitergeben möchten.

Sie können feststellen, dass die Umleitung stattgefunden hat, und die Anforderung direkt an den richtigen Speicherort erneut ausgeben.

Ihre API gibt 401 Unauthorized zurück, um anzugeben, dass der Autorisierungsheader fehlt (oder unvollständig). Ich nehme an, dass dieselbe API 403 Forbidden zurückgibt, wenn die Autorisierungsinformation in der Anfrage vorhanden ist, aber einfach falsch ist (falscher Benutzername/falsches Passwort).

Wenn dies der Fall ist, können Sie die Kombination "redirect/missing authorization header" erkennen und die Anfrage erneut senden.


Hier ist der Code von der Frage, dies zu tun neu geschrieben:


Hinweis

[Test] 
public void RedirectTest() 
{ 
    // These lines are not relevant to the problem, but are included for completeness. 
    HttpResponseMessage response; 
    var client = new HttpClient(); 
    using (var authString = new StringContent(@"{username: ""theUser"", password: ""password""}", Encoding.UTF8, "application/json")) 
    { 
     response = client.PostAsync("http://host/api/authenticate", authString).Result; 
    } 

    string result = response.Content.ReadAsStringAsync().Result; 
    var authorization = JsonConvert.DeserializeObject<CustomAutorization>(result); 

    // Relevant from this point on. 
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Token); 
    client.DefaultRequestHeaders.Add("Accept", "application/vnd.host+json;version=1"); 

    var requestUri = new Uri("http://host/api/getSomething"); 
    response = client.GetAsync(requestUri).Result; 

    if (response.StatusCode == HttpStatusCode.Unauthorized) 
    { 
     // Authorization header has been set, but the server reports that it is missing. 
     // It was probably stripped out due to a redirect. 

     var finalRequestUri = response.RequestMessage.RequestUri; // contains the final location after following the redirect. 

     if (finalRequestUri != requestUri) // detect that a redirect actually did occur. 
     { 
      if (IsHostTrusted(finalRequestUri)) // check that we can trust the host we were redirected to. 
      { 
       response = client.GetAsync(finalRequestUri).Result; // Reissue the request. The DefaultRequestHeaders configured on the client will be used, so we don't have to set them again. 
      } 
     } 
    } 

    Assert.True(response.StatusCode == HttpStatusCode.OK); 
} 


private bool IsHostTrusted(Uri uri) 
{ 
    // Do whatever checks you need to do here 
    // to make sure that the host 
    // is trusted and you are happy to send it 
    // your authorization token. 

    if (uri.Host == "host") 
    { 
     return true; 
    } 

    return false; 
} 
, dass Sie den Wert finalRequestUri und für zukünftige Anforderungen verwenden retten könnten die zusätzliche Anforderung beteiligt zu vermeiden in der Wiederholung. Da es sich um eine temporäre Weiterleitung handelt, sollten Sie die Anfrage wahrscheinlich jedes Mal an den ursprünglichen Speicherort senden.

+0

Danke für die tolle Antwort. Beispiel ist sehr hilfreich. – Vadim

+0

Danke für die Erklärung. – jNet

0

Ich hatte ein ähnliches Problem, aber nicht ganz das Gleiche. In meinem Fall hatte ich auch das Redirect-Problem, aber die Sicherheit wird mit OAuth implementiert, was auch das sekundäre, aber verwandte Problem hat, dass Tokens manchmal ablaufen.

Aus diesem Grunde würde Ich mag Lage sein, ein HttpClient automatisch zu konfigurieren, um die OAuth-Token gehen und aktualisiert werden, wenn es eine empfängt 401 Unauthorized Antwort, und zwar unabhängig davon, ob dies aufgrund einer Umleitung geschieht, oder einen Token Ablauf.

Die von Chris O'Neill veröffentlichte Lösung zeigt die allgemeinen Schritte, aber ich wollte dieses Verhalten in ein HttpClient Objekt einbetten, anstatt den gesamten HTTP-Code mit einer imperativen Überprüfung zu umgeben.Wir haben viel vorhandenen Code, der ein gemeinsames HttpClient-Objekt verwendet, so dass es viel einfacher wäre, unseren Code umzuformen, wenn ich das Verhalten dieses Objekts ändern könnte.

Das folgende sieht so aus, als ob es funktioniert. Ich habe es bisher nur entwickelt, aber es scheint zu funktionieren. Ein großer Teil unserer Code-Basis ist in F #, so dass der Code ist in F #:

open System.Net 
open System.Net.Http 

type TokenRefresher (refreshAuth, inner) = 
    inherit MessageProcessingHandler (inner) 

    override __.ProcessRequest (request, _) = request 

    override __.ProcessResponse (response, cancellationToken) = 
     if response.StatusCode <> HttpStatusCode.Unauthorized 
     then response 
     else 
      response.RequestMessage.Headers.Authorization <- refreshAuth() 
      inner.SendAsync(response.RequestMessage, cancellationToken).Result 

Dies ist eine kleine Klasse, die der Auffrischung des Authorization Kopf kümmert, wenn sie eine 401 Unauthorized Antwort erhält. Es wird mit einer injizierten refreshAuth-Funktion aktualisiert, die den Typ unit -> Headers.AuthenticationHeaderValue hat.

Da dies immer noch Prototyp-Code ist, machte ich die innere SendAsync Anruf einen blockierenden Anruf, so dass es eine Übung für den Leser, um es richtig mit einem asynchronen Workflow zu implementieren.

eine Refresh-Funktion refreshAuth, genannt Da Sie ein neues HttpClient Objekt wie folgt erstellen:

let client = new HttpClient(new TokenRefresher(refreshAuth, new HttpClientHandler())) 

Die Antwort geschrieben von Chris O'Neill kümmert, dass die neue URL noch zu prüfen, als sicher angesehen wird. Ich habe diese Sicherheitsüberlegung hier ausgelassen, aber Sie sollten einen ähnlichen Check einbeziehen, bevor Sie die Anfrage wiederholen.

+0

Dies löst jedoch ein anderes Problem (Token-Aktualisierung). – MvdD

+0

@MvdD Das hängt davon ab, wie Sie 'refreshAuth' implementieren. –

+0

Sicher, aber in der Regel möchten Sie das Token nur aktualisieren, wenn es abgelaufen ist. Im OP-Fall war das Token nicht abgelaufen, wurde aber aus der umgeleiteten Anforderung weggelassen. – MvdD

1

Ich würde das automatische Weiterleitungsverhalten deaktivieren und einen Client-Handler erstellen, der den Code für die temporäre Weiterleitung verbirgt. Mit der Klasse HttpClient können Sie DelegatingHandler s installieren, von denen Sie die Antwortanforderung ändern können.

public class TemporaryRedirectHandler : DelegatingHandler 
{ 
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) 
    { 
     var response = await base.SendAsync(request, cancellationToken); 
     if (response.StatusCode == HttpStatusCode.TemporaryRedirect) 
     { 
      var location = response.Headers.Location; 
      if (location == null) 
      { 
       return response; 
      } 

      using (var clone = await CloneRequest(request, location)) 
      { 
       response = await base.SendAsync(clone, cancellationToken); 
      } 
     } 
     return response; 
    } 


    private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request, Uri location) 
    { 
     var clone = new HttpRequestMessage(request.Method, location); 

     if (request.Content != null) 
     { 
      clone.Content = await CloneContent(request); 
      if (request.Content.Headers != null) 
      { 
       CloneHeaders(clone, request); 
      } 
     } 

     clone.Version = request.Version; 
     CloneProperties(clone, request); 
     CloneKeyValuePairs(clone, request); 
     return clone; 
    } 

    private async Task<StreamContent> CloneContent(HttpRequestMessage request) 
    { 
     var memstrm = new MemoryStream(); 
     await request.Content.CopyToAsync(memstrm).ConfigureAwait(false); 
     memstrm.Position = 0; 
     return new StreamContent(memstrm); 
    } 

    private void CloneHeaders(HttpRequestMessage clone, HttpRequestMessage request) 
    { 
     foreach (var header in request.Content.Headers) 
     { 
      clone.Content.Headers.Add(header.Key, header.Value); 
     } 
    } 

    private void CloneProperties(HttpRequestMessage clone, HttpRequestMessage request) 
    { 
     foreach (KeyValuePair<string, object> prop in request.Properties) 
     { 
      clone.Properties.Add(prop); 
     } 
    } 

    private void CloneKeyValuePairs(HttpRequestMessage clone, HttpRequestMessage request) 
    { 
     foreach (KeyValuePair<string, IEnumerable<string>> header in request.Headers) 
     { 
      clone.Headers.TryAddWithoutValidation(header.Key, header.Value); 
     } 
    } 
} 

Sie würden instanziiert die Httpclient wie folgt aus:

var handler = new TemporaryRedirectHandler() 
{ 
    InnerHandler = new HttpClientHandler() 
    { 
     AllowAutoRedirect = false 
    } 
}; 

HttpClient client = new HttpClient(handler); 
+0

Warum sollten Sie automatische Weiterleitungen deaktivieren? –

+0

@MarkSeemann So kann ich sie in dem Client-Handler, den ich installiere, selbst behandeln. – MvdD

Verwandte Themen