Angular 7 自动刷新不记名令牌

Posted

技术标签:

【中文标题】Angular 7 自动刷新不记名令牌【英文标题】:Angular 7 auto-refresh bearer token 【发布时间】:2019-06-29 10:23:11 【问题描述】:

我有一个 AuthService,主要有两种方法:

getAuthToken(返回 Promise,因此可以延迟调用/多次调用,同时阻塞等待单个集合)

refreshToken(也返回一个 Promise,使用原始 JWT 上可用的刷新令牌来请求新的身份验证令牌)

我想自动

将不记名令牌应用于每个 http 请求(工作) 在刷新时刷新令牌——我快到了,除了带有刷新令牌的请求结果不会返回给原始订阅者。

代码如下:

import  HttpEvent, HttpHandler, HttpHeaders, HttpInterceptor, HttpRequest  from "@angular/common/http";
import  from, Observable  from "rxjs";
import  Injectable  from "@angular/core";
import  AuthService  from "./auth.service";

@Injectable()
export class AuthHttpInterceptor implements HttpInterceptor 
  constructor(
    private _authService: AuthService,
  ) 
  

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
    return from(this.addBearerToken(req, next));
  

  private async addBearerToken(req: HttpRequest<any>, next: HttpHandler): Promise<HttpEvent<any>> 
    const token = await this._authService.getAuthToken();

    const headerSettings = req.headers.keys().reduce(
      (acc, cur) => 
        acc[cur] = req.headers.getAll(cur);
        return acc;
      , );

    if (token) 
      headerSettings["Authorization"] = `Bearer $ token `;
     else 
      console.log("performing request without auth!");
    
    // prevent 302 redirect to challenge on a 401
    headerSettings["X-Requested-With"] = "XMLHttpRequest";
    const
      headers = new HttpHeaders(headerSettings),
      newRequest = req.clone( headers );
    const result = next.handle(newRequest).toPromise();
    result.catch(async (err) => 
      if (err.status === 401) 
        const
          newToken = await this._authService.refreshToken();
        headerSettings["Authorization"] = `Bearer $ newToken `;
        const
          updatedHeaders = new HttpHeaders(headerSettings),
          updatedRequest = req.clone( headers: updatedHeaders );
        console.log("requery with new token"); // <-- I see this when I have a 401, eg by altering the auth token to be bad, whilst leaving the refresh token alone
        return next.handle(updatedRequest).toPromise().then(data => 
          console.log("requeried data:", data); // <-- I also see this fire, with the valid data coming back from the second request
          return data; // <-- however the original caller doesn't get this data
        );
      
    );
    return result;

  

我不得不假设这可能是由于我混合了 Observables 和 Promises(我这样做是因为 AuthService 是异步的,使用 Promises)。此外,如果没有 401,则原始调用确实通过正确 - 就好像承诺链在该行之后被丢弃

next.handle(newRequest).toPromise();

我错过了什么?

【问题讨论】:

可以切换到 Observables 吗?而且,如果你在最后返回承诺会发生什么: return next.handle(updatedRequest).toPromise() 从原始的.toPromise() 返回有效——对于没有 401 错误的请求,上面的代码也有效。切换到 Observables 可能是可能的——但会影响其他期望在这里得到承诺的领域;身份验证服务还存储解决最后一个有效令牌的承诺,以便后续调用可以“按原样”获得最后一个正确答案 - 如果有多个调用者试图获取令牌并且第一个调用者触发了刷新。 Promise 让这变得非常简单——我不知道如何将其转化为 Observables,tbh。 【参考方案1】:

你在这里打破了链条:

const result = next.handle(newRequest).toPromise();
result.catch(async (err) => 
  ...
);

return result;

result.catch 返回一个新的 Promise,并且您的处理程序不会等待您在 catch 中调用的所有操作。

所以你可以这样写:

const result = next.handle(newRequest).toPromise();
return result.catch(async (err) => 
  ...
);

另外你可能想要做的是不要在进行中多次调用 refreshToken

cachedRequest: Promise<any>; // define prop in your class
...

if (!this.cachedRequest) 
  this.cachedRequest = this.authService.refreshToken();

const  newToken = await this.cachedRequest;
this.cachedRequest = null;

这是Simple Demo,您可以对其进行测试。 (我在那里处理 404 但没关系

【讨论】:

很好发现——这是导致问题的断链)':另外,authService 已经处理了多请求场景——这就是它是异步的原因(即返回一个承诺) -- 如果一个身份验证查询已经在进行中(或者最后一个已经工作了,调用者只需取回正在进行的或有效的承诺,所以我不必关心缓存请求或类似的事情(: (另外,我不得不承认,我被这件事难住了,我觉得很傻——我不仅多年来一直使用 Promise,我还编写了 (4, iirc) Promise 实现,两次为了兴趣,两次(包括重写)npmjs.com/package/synchronous-promise——你会认为我会发现一个明显的错误):仍然,再次感谢@yurzui!跨度>

以上是关于Angular 7 自动刷新不记名令牌的主要内容,如果未能解决你的问题,请参考以下文章

Angular JWT 拦截器切换承载令牌以进行刷新

Jhipster 隐藏不记名令牌

在 asp.net vnext 上使用不记名令牌身份验证刷新令牌

不记名令牌未添加到 HTTP 请求 - MSAL2 Angular

Angular 8:如何在没有刷新令牌的情况下刷新令牌

JWT 刷新令牌 Angular