Angular 5 Http 拦截器刷新 JWT 令牌

6

我已经实现了令牌保存、检索和刷新的逻辑。问题在于当我拦截 HttpInterceptor 中的 403 错误时,同时发出的其他调用也会刷新令牌。我希望能够保留这些调用,直到我的令牌刷新完成。为此,我需要创建一个“信号量”来控制请求。

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

private auth: AuthService;

constructor(private injector: Injector) {
}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.auth = this.injector.get(AuthService);

    if(this.auth.isAuthenticated()){
        request = request.clone({
            setHeaders: {
                Accept: 'application/json',
                Authorization: `Bearer ${localStorage.getItem('access_token')}`
            }
        });
    } else {
        request = request.clone({
            setHeaders: {
                Accept: 'application/json'
            }
        });
    }

    return next.handle(request).catch(error => {
        if (error.status === 401) {
            console.log('refreshing token');

            // TODO: return Refresh Token here and hold other calls
        }

        return Observable.throw(error);
    });
}

1
你能发布一下你的 HttpInterceptor 代码吗? - Dmitry
好的,我已经更新了我的问题。不知道为什么会有这么多负评。 - Bartando
你确定在收到 401 后刷新令牌是一个好主意吗?如果您使用 JWT 令牌的过期时间来确定在它有机会过期之前何时刷新,这个问题将不存在。 - Vojtech
我认为为了更安全,令牌应该在一段时间后过期。 - Bartando
是的,令牌会在一段时间后过期,但它的过期信息应该被编码在令牌中,并在前端解析和用于及时更新令牌。 - Vojtech
@Bartando,请问一下,下面的解决方案有效吗? - G_S
1个回答

6

很抱歉我无法设置环境来测试这个逻辑是否正常工作,但我会尽力:

您的拦截器应该像这样:

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  private auth: AuthService;

  constructor(private injector: Injector) {
    this.auth = this.injector.get(AuthService);
  }

  setHeaders(request) {
    return function (token) {
      return request.clone({
        setHeaders: {
          Accept: 'application/json',
          Authorization: `Bearer ${token}`
        }
      });
    }
  }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.auth
      .getToken()
      .map(this.setHeaders(request))
      .mergeMap(next.handle)
      .catch(error => {
        if (error.status === 401) {
          return this.auth.refreshToken()
            .map(this.setHeaders(request))
            .mergeMap(next.handle);
        }
        return Observable.throw(error);
      });
  }

}

而你的AuthService:

@Injectable()
export class AuthService {

  private refreshTokenCall;

  saveTokenInLocalStorage(token) {
    localStorage.setItem('access_token', token);
  }

  getToken() {
    if (localStorage.getItem('access_token')) {
      return Observable.of(localStorage.getItem('access_token'));
    }

    return this.refreshToken();
  }

  refreshToken() {
    if (!this.refreshTokenCall) {
      this.refreshTokenCall = this.http.get(refreshTokenURL)
        // Maybe a .map() here, it depends how the backend returns the token
        .do(this.saveTokenInLocalStorage)
        .finally(() => this.refreshTokenCall = null);
    }
    return this.refreshTokenCall;
  }

}

我希望它在某种程度上对你有所帮助。


我会尝试这个,如果它有效,赏金就是你的 :) 谢谢 - Bartando
如果能加上解释,这个答案会更好。那些调用是如何等待令牌刷新的? - Gabriel Luca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接