【问题标题】:Angular 2 Refresh access token on 401 error and repeat initial requestAngular 2在401错误时刷新访问令牌并重复初始请求
【发布时间】:2017-10-09 01:36:51
【问题描述】:

TLDR:我的任务是完成 3 个请求而不是 1 个,并将最后一个响应作为对第一个请求的响应返回,而不需要对请求发起者进行任何额外的修改。

我已扩展 Angular Http 类以自动将授权标头附加到我的所有请求并实现我自己的授权错误处理。

看起来像这样:

  request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {

    // ... append some headers

    super.request(url, options).catch((error: Response) => {
      if (error.status === 401 || error.status === 403 ) {
        // todo: Send refreshToken request to get new credentials
        // todo: Send current request again with new credentials

        // todo: If request is completed properly pretend everything was fine and return response
      }
    });
  }

我想捕获授权错误,通过发送令牌刷新请求来修复它们并对初始请求返回正确的响应。

现在有很多代码使用http,我不想更改它,因此必须返回固定响应,因为初始响应会在没有人知道的情况下返回。

其中一种方法是使用同步请求,但我认为这不是一个好主意。

您能告诉我解决方案是否可行,我该如何实现?

附言。如果在刷新令牌时执行另一个请求并崩溃到授权导致再次刷新令牌,则可能会出现问题。但这现在已经不那么重要了。

【问题讨论】:

  • 没有其他方法可以检查您的令牌是否过期?
  • 其实我已经按照我想要的方式解决了这个任务。我将描述我的解决方案并在不久的将来将其发布在这里。诀窍在于使用flatmap 将“内部”响应嵌套到“外部”中。
  • 你忘记发布更新了。
  • @AvramVirgil 我已经发布了我的解决方案作为答案。想看就看
  • @Robin Dijkhof 否,因为它可能会在您检查后,在执行实际请求之前过期

标签: javascript angular oauth rxjs


【解决方案1】:

主要是通过使用flatMap 来编写请求来实现的。

主要功能:

  • 检查request请求是否返回401
  • 如果 401:尝试修复更新必要的令牌并再次发送请求
  • 如果错误已修复,订阅者对错误一无所知

它旨在与 REST 身份验证模型一起使用,其中包括:

  • 访客令牌 - 用于未经授权的用户 (gToken)
  • 身份验证令牌 - 用于授权用户 - (aToken)
  • refresh token - 刷新过期的aToken (refresh_token)

您很可能需要重写请求以适应您的后端,但这里提供了一个注释良好的服务,而不是默认的Http

import {Injectable} from '@angular/core';
import {
  Http, XHRBackend, RequestOptions, RequestOptionsArgs, Request, Response, RequestMethod,
  Headers
} from "@angular/http";
import { Observable } from "rxjs";
import { StorageService } from "../storage.service";
import { AppService } from "./app.service";

@Injectable()
export class HttpClientService extends Http {

  private autoAppendHeadersDefault = true;

  constructor(
    backend: XHRBackend,
    defaultOptions: RequestOptions,
    private storageService: StorageService,
    private appState: AppService,
  ) {
    super(backend, defaultOptions);
    this.autoAppendHeadersDefault = this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS;
  }

  request(url: string | Request, options?: RequestOptionsArgs, disableTryFix = false): Observable<Response> {

    // Checking if the request needs headers to be appended
    let assetRequest = false;
    if(url instanceof Request) {
      if(url.url.startsWith("/assets")) {
        assetRequest = true;
      }
    }

    // Appending headers
    if(!assetRequest && this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS && url instanceof Request) {

      // append aToken || gToken
      let token = this.storageService.get('aToken');
      if('undefined' === typeof token || !token) {
        token = this.storageService.get('gToken');
      }

      if('undefined' !== typeof token && token) {
        url.headers.set('Authorization', `Bearer ${token}`);
      } else {
        // neither aToken nor gToken are set
        if(disableTryFix) {
          this.removeAllTokens();
          return Observable.throw({error: "Can't reauth: 01"});
        }
        return this.tryFixAuth().flatMap(
          (res:any) => {
            res = res.json();
            this.storageService.set('gToken', res.access_token);
            return this.request(url, options, true);
          }
        );
      }

      // headers appended to every request
      if(!url.headers.get('Content-Type')) {
        url.headers.append('Content-Type', 'application/json');
      }
    }
    this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = this.autoAppendHeadersDefault;

    return super.request(url, options).catch((error: Response) => {
      if (error.status === 401 /* || error.status === 403 */ ) {

        if(disableTryFix) {
          this.removeAllTokens();
          this.navigateOnAuthFail();
          return Observable.throw({error: "Can't reauth: 02"});
        }

        return this.tryFixAuth().flatMap(
          (res: any) => {
            res = res.json();

            if('undefined' !== typeof res.refresh_token)
            {
              // got aToken & refresh_token
              this.storageService.set('aToken', res.access_token);
              this.storageService.set('refresh_token', res.refresh_token);
            }
            else if('undefined' !== typeof res.access_token)
            {
              // got only gToken
              this.storageService.set('gToken', res.access_token);
            }
            else
            {
              console.log('tryFix: nothing useful returned')
              // got no aToken, no gToken, no refresh_token
            }

            // retry request
            return this.request(url, options, true);
          }
        );
      }

      // handle invalid refresh_token
      if(disableTryFix && error.status === 400) {
        console.log('Wrong refresh token (400)');
        this.storageService.remove('refresh_token');
        this.storageService.remove('aToken');
        this.navigateOnAuthFail();
        // handle invalid refresh token
      }
      return Observable.throw(error);
    });
  }

  private tryFixAuth(): Observable<Response> {
    console.log('Trying to fix auth');

    if(this.storageService.get('refresh_token'))
    {
      return this.refreshToken();
    }
    else if(this.storageService.get('aToken'))
    {
      // no refresh_token, but aToken
      // since aToken is dead it's not useful
      this.storageService.remove('aToken');
    }
    else
    {
      // no aToken, no refresh_token
      // possibly there's a gToken
      // since the request is trying to fix itself (is failed) the gToken is most likely not valid
      return this.guestToken();
    }
  }

  // sends request with refresh_token to get new aToken
  // the request returns only aToken and refresh_token, no gToken
  private refreshToken(): Observable<Response> {

    // is called only when refresh_token is set
    let refreshToken = this.storageService.get('refresh_token');

    // check refresh_token in case it's not checked before
    if('undefined' === typeof refreshToken || !refreshToken || refreshToken == 'undefined') {
      this.storageService.remove('refresh_token');
      // there's no refresh_token saved
      return Observable.throw({error: "Refresh token is not set"});
    }

    // form refresh_token request
    const headers = new Headers();
    headers.append('Authorization', `Bearer ${this.storageService.get('gToken')}`);
    headers.append('Content-Type', 'application/json');

    const url = `${this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token`;
    const localData = JSON.stringify({
      "client_id": this.appState.config.CLIENT_ID,
      "client_secret": this.appState.config.CLIENT_SECRET,
      "grant_type": 'refresh_token',
      "refresh_token": refreshToken
    });

    this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;

    // refresh_token request
    return this.request(
      new Request({
        method: RequestMethod.Post,
        url: url,
        headers: headers,
        body: localData
      }),
      null, true);
  }

  // sends request to get new gToken
  private guestToken(): Observable<Response> {
    const url = `${
      this.appState.config.WEBSITE_ENDPOINT}/oauth/v2/token?client_id=${
      this.appState.config.CLIENT_ID}&client_secret=${
      this.appState.config.CLIENT_SECRET}&grant_type=client_credentials`;
    this.appState.hoodConfig.HTTP_AUTO_APPEND_HEADERS = false;
    return super.get(url);
  }


  // Aux methods

  private navigateOnAuthFail() {
    console.warn('Page is going to be refreshed');

    // redirect to auth is performed after reload by authGuard
    // it's possible to add some warning before reload
    window.location.reload();
  }

  private removeAllTokens() {
    this.storageService.remove('aToken');
    this.storageService.remove('gToken');
    this.storageService.remove('refresh_token');
  }
}

【讨论】:

  • @PrathapG 很高兴知道 :) 实际上代码现在已经过时了。我可以稍后发布一个现代且更清洁的拦截器方式。
  • 我找了一个多星期的答案,我正在使用 Angular 5,但使用 HTTPClient 时出现问题,所以我不得不实现旧的 HTTP 拦截器。带有刷新令牌。赞一个!
猜你喜欢
  • 2018-05-05
  • 2021-11-26
  • 2019-11-08
  • 1970-01-01
  • 2019-09-19
  • 2014-02-15
  • 2021-08-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多