2017-06-30 7 views
0

Ich versuche, die eingebaute request Methode in eckigen Http zu schmücken. Ich trigle eine Vielzahl von angeketteten und/oder asynchronen Anfragen zur gleichen Zeit, was wiederum dazu führt, dass die 401 Antwort viele Male gleichzeitig ausgelöst wird. Dies führt zum Absturz der Autorisierung, da dasselbe Refresh-Token mehrmals verwendet wird.Angular 4 http Interceptor refresh_token ausgelöst zu oft

Bisher habe ich so weit gekommen:

request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { 
    return super.request(url, options).catch(error=> { 
     if (error.status === 401) { 
      return Observable.create((observer: Observer<Response>) => { 
       // Use promise to avoid OPTIONS request 
       this.oauthService.refreshToken().then((tokenResponse: Response) => { 
        console.debug('promise resolved'); 

        observer.next(tokenResponse); 
        observer.complete(); 
       }); 
      }).mergeMap((tokenResponse) => { 
       options = this.updateAuthHeader(options); 
       return super.request(url, options); 
      }); 
     } else { 
      return Observable.throw(error); 
     } 
    }); 
} 

Mein Problem scheint zu sein, dass ich brauche, um die anstehenden Anforderungen an que, wenn ein 401 aufgetreten ist, oder dass ich fehlt nur das Wissen, wie um das mit Observablen zu erreichen. Soweit ich das verstehe, sollte die mergeMap für mich, aber leider alle die Anfrage auslösen, bevor mein Token aktualisiert wurde.

Irgendwelche Ideen, wie man das erreicht?

+0

Nur sagen, wenn Ihre Anfrage tatsächlich erfolgreich ist (aka nicht 401), werfen Sie einen Fehler; was möglicherweise kein gewünschtes Verhalten ist. Schnelle Lösung: Entferne das 'else' – CozyAzure

+0

Nun .. tue ich? Es ist innerhalb einer .catch() Hexe wirft den Fehler, nicht die erfolgreiche Anfrage –

+0

Werfen Sie einen Blick auf meine Antwort hier, wo ich die gleiche Frage beantworten: https://stackoverflow.com/questions/42390773/angular2-get-and-cache -token-only-once/42394195 # 42394195 –

Antwort

2

Mit Hilfe von @ stely000, endete einige Kollegen und anderen Online-Communities ich auf den Punkt:

Da ich hier mein Code zum Abfangen des integrierten HTTP-Service von angular2/angular4 https://scotch.io/@kashyapmukkamala/using-http-interceptor-with-angular2 fand die Lösung bin mit unterscheidet sich ein wenig von anderen Lösungen, die ich gefunden habe. Die OAuthService ich an verschiedenen Orten verweisen kann hier gefunden werden: https://github.com/manfredsteyer/angular-oauth2-oidc. Aufgrund von Abhängigkeiten in diesem Dienst zu Http musste ich diesen Dienst nachher injizieren, um zirkuläre Abhängigkeiten zu vermeiden. Wenn Sie Fragen dazu haben, fragen Sie mich einfach und ich werde das auch beantworten. :)

Im Grunde ist dies meine Lösung für die refresh_token Funktionalität einmal auszulösen, wenn der Back-End-Dienst mit 401 reagiert in drei Schritten erreicht:

  1. eine beobachtbare erstellen, die this.postRequest zu vermeiden geteilt wird () um mehr als einmal zu triggern.

  2. Erstellen Sie Anforderungsheader und fügen Sie einen Beitrag zum Endpunkt hinzu, wobei refresh_token behandelt wird.

  3. Den freigegebenen Beobachter von Schritt 1 anhören. Wenn das Token aktualisiert wurde, extrahieren und aktualisieren Sie Daten im lokalen Speicher.

Jetzt, in meinem Konstruktor ich die beobachtbare shared:

constructor(
    backend: ConnectionBackend, 
    defaultOptions: RequestOptions, 
    private injector: Injector 
) { 
    super(backend, defaultOptions); 
    // Step 1: Create an observable that is shared to avoid this.postRequest() to trigger more than once at a time 
    this.refreshTokenObserver = Observable.defer(() => { 
     return this.postRequest(); 
    }).share(); 
} 

Dann erstelle ich eine Methode für die Anforderung der Buchung des access_token (Hinweis zu aktualisieren: diese im Grunde eine Kopie des Codes ist, dass wird in der OAuthService gehalten.Dies liegt daran, das Verfahren für den in diesem Dienst ist nicht öffentlich):

// This method will only be triggered once at a time thanks to she shared observer above (Step 1). 
private postRequest(): Observable<any> { 
    // Step 2: Create request headers and add post to endpoint where refresh_token is handled. 
    let search = new URLSearchParams(); 
    search.set('grant_type', 'refresh_token'); 
    search.set('client_id', this.oauthService.clientId); 
    search.set('scope', ''); 
    search.set('refresh_token', localStorage.getItem('refresh_token')); 

    let headers = new Headers(); 
    headers.set('Content-Type', 'application/x-www-form-urlencoded'); 

    let params = search.toString(); 

    return super.post(this.oauthService.tokenEndpoint, params, { headers }).map(r => r.json()); 
} 

Dann habe ich ein Verfahren zum Extrahieren von Daten und Aktualisierung des lokalen Speichers mit der Antwort dann Endpunkt:

// This method is triggered when the server responds with 401 due to expired access_token or other reasons 
private refreshToken() { 
    // Step 3: Listen to the shared observer from step 1. When the token has been refreshed, extract and update data in localstorage 
    return this.refreshTokenObserver.do((tokenResponse) => { 
     localStorage.setItem("access_token", tokenResponse.access_token); 
     if (tokenResponse.expires_in) { 
      var expiresInMilliSeconds = tokenResponse.expires_in * 1000; 
      var now = new Date(); 
      var expiresAt = now.getTime() + expiresInMilliSeconds; 
      localStorage.setItem("expires_at", "" + expiresAt); 
     } 

     if (tokenResponse.refresh_token) { 
      localStorage.setItem("refresh_token", tokenResponse.refresh_token); 
     } 
    }, 
    (err) => { 
     console.error('Error performing password flow', err); 
     return Observable.throw(err); 
    }); 
} 

In um die oben genannten Schritte, die anfängliche Anforderung zu initiieren muss ausgelöst werden und mit einem 401 reagieren:

request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> { 
    return super.request(url, options).catch(error=> { 
     if (error.status === 401) { 
      // It the token has been expired trigger a refresh and after that continue the original request again with updated authorization headers. 
      return this.refreshToken().mergeMap(() => { 
       options = this.updateAuthHeader(options); 
       return super.request(url, options); 
      }); 
     } else { 
      return Observable.throw(error); 
     } 
    }); 
} 

Bonus: die Methode, die ich verwende für die A Aktualisierung uthorization Header verwendet grundsätzlich die Funktionalität in der OAuthService oben erwähnt:

private updateAuthHeader(options: RequestOptionsArgs) { 
    options.headers.set('Authorization', this.oauthService.authorizationHeader()); 

    return options; 
} 

Reflections/Gedanken: Die ursprüngliche Idee von meiner Seite die OAuthService zu verwenden war Token zu aktualisieren. Dies war schwieriger als ich erwartet hatte aufgrund der Mischung aus Versprechungen und Observables. Ich kann wahrscheinlich die postRequest Methode ändern, um die erwähnten Service-Methoden zu verwenden. Ich weiß nicht wirklich, was die bessere/sauberere Lösung sein könnte.

Auch denke ich, dass dies etwas für alle sein sollte, um eine einfache Lösung für zu finden. Dies war alleine schwer zu erreichen und ich danke allen, die mir geholfen haben (sowohl hier in SO, IRL als auch in anderen Gemeinden).

Verwandte Themen