【问题标题】:Angular5 Http Interceptor with refresh_token带有 refresh_token 的 Angular5 Http 拦截器
【发布时间】:2018-01-18 16:43:51
【问题描述】:

我正在尝试在 Angular5 中的 Http 拦截器中实现 access_token/refresh_token。我遇到的问题是在获得刷新令牌后我无法再次拨打电话。 因此,当我收到 401 错误(access_token 已过期)时,我使用 refresh_token 获得了一个新的 access_token,直到这里一切正常。接下来,我应该使用新的 access_token 再次进行初始调用,因此我尝试使用第二个来执行此操作

return next.handle(req);

但这不起作用。所以当我第一次点击并且令牌过期时,应用程序成功获取了一个新令牌并将其写入localStorage,但没有再次进行初始调用。如果我再次单击,则初始调用成功(使用在上一次单击时正确存储的新访问令牌)。就像我无法摆脱困境一样。也许我犯了一个愚蠢的错误。

下面是我的代码。请指教。谢谢!

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    let access_token, refresh_token;
    if (localStorage.currentUser) {
        access_token = JSON.parse(localStorage.currentUser).access_token;
        refresh_token = JSON.parse(localStorage.currentUser).refresh_token;

        if(access_token){
            req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + access_token) });
        }
    }
    return next.handle(req)
        .catch((error, caught) => {
            if (this.isAuthError(error)) {
                return this._auth.getRefreshToken().map((resp) => {
                    let user = resp.json();
                    localStorage.setItem('currentUser', JSON.stringify(user));
                    access_token = JSON.parse(localStorage.currentUser).access_token;
                    req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + access_token) });
                    return next.handle(req);
                }); 
            } else {
                return Observable.throw(error);
            }
        })
}

【问题讨论】:

    标签: angular refresh-token


    【解决方案1】:

    你需要使用 flatMap() 代替 map() 如下所示:

            ....
            if (this.isAuthError(error)) {
                return this._auth.getRefreshToken().flatMap((resp) => {
            ....                                    ^^^^^^^
                                                     here
    

    否则你的解决方案对我来说看起来不错。

    【讨论】:

    • 感谢@Alexander!看来flatMap 成功了。我还必须添加import 'rxjs/add/operator/mergeMap'; 才能使用flatMap,但我相信这取决于Angular 版本。非常感谢您的帮助!
    • @decebal,如果有效,请接受答案。我很高兴我能够提供帮助。 :)
    【解决方案2】:

    在您的拦截中,您需要设置标题。

    if(access_token){
                req = req.clone({setHeaders: 
                {Authorization: `Bearer ${access_token}`}
            });
    }
    

    您需要对另一个令牌执行相同的操作。

    【讨论】:

    • 我已经这样做了。无论如何,这很好用。我遇到的问题是来自catch 的代码。
    【解决方案3】:

    我做了类似的事情:

    if ( this._auth.isValidToken() )
      token = this._auth.getValidToken();
      req = this.addToken(req, this._auth.getToken());
      return next.handle(req);
    }
    
    // The getNewAccessToken is an observable
    return this._auth.getNewAccessToken().pipe(switchMap(() => {
        const clone: HttpRequest<any> = this.addToken(req, this._auth.getToken());
        return next.handle(clone);
    })).catch(error => {
      if ( error.error === 'invalid_token' ) { // if it fails to refresh the token
        // Redirect to Login, but you could also cache the url
        this.router.navigate(['/login']);
        return Observable.throw('Login expired');
    
      }
      console.log(error);
      return Observable.throw(error);
    });
    

    getNewAccessToken() 函数如下所示:

      public getNewAccessToken(): Observable<string>  {
        const headers = new HttpHeaders()
        .set('Content-Type', 'application/x-www-form-urlencoded') // FIXME: Replace Encoded Base64 for a custom version
        .set('Authorization', 'Basic WQQPWEOIRUPOIUQ=');
    
        const params = new HttpParams().set('grant_type', 'refresh_token');
        const httpOptions = { headers: headers, params: params, withCredentials: true };
    
        return this.httpClient.post( this.oauthTokenUrl,  new FormData(), httpOptions )
          .map(success => {
            this.storeToken(success['access_token']);
          })
          .catch(this.handleError);
      }
    

    storeToken 函数:

      public storeToken(token: string) {
        this.jwtPayload = this.jwtHelper.decodeToken(token);
        localStorage.setItem('token', token);
        this.subjectUser.next( { name: this.jwtPayload.name, email: this.jwtPayload.user_name } );
      }
    

    【讨论】:

    • 所以在getNewAccessToken() 中您检索新的access_token,但是您在哪里使用它从API 中获取您需要的数据?我获得新令牌没有问题,我的问题是在获得新的access_token 后,我无法从“catch”内部发出初始 HTTP 请求。此外,在您的代码中,您不会在任何地方调用 getNewAccessToken()
    • 我修复了 getNewAccessToken() 调用。顺序是:
    • 我修复了示例中的 getNewAccessToken() 调用。我在使用它时也遇到了问题,因为我不太清楚如何使用可观察的异步调用。顺序是: - 检查令牌是否有效。如果有效,则执行原始的http请求。完毕。 - 如果令牌无效,则调用新令牌的请求,作为可观察对象,当您获得新令牌时(在可观察对象的 .pipe(...) 内部)将令牌添加到标头并执行原始令牌http 请求。
    • 好的,谢谢!我设法通过使用 flatMap() 而不是 'map()` 来修复它。
    猜你喜欢
    • 1970-01-01
    • 2023-03-13
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-11-07
    • 2021-02-22
    相关资源
    最近更新 更多