在发出克隆请求之前更新令牌

Posted

技术标签:

【中文标题】在发出克隆请求之前更新令牌【英文标题】:Renew token before making cloned request 【发布时间】:2020-10-07 02:51:44 【问题描述】:

所以我想在令牌过期时使用刷新令牌更新访问令牌,这就是我的令牌拦截器的样子:

intercept(req: HttpRequest<any>, next: HttpHandler) 
    let authservice = this.injector.get(AuthService)
      let tokenreq = req.clone(
        setHeaders: 
          Authorization: `Bearer $authservice.setToken()`
        
      )
      return next.handle(tokenreq).pipe(
        catchError((error) => 
          if (error.error.message == 'The incoming token has expired') 
            authservice.renewToken()
            let tokenreq = req.clone(
              setHeaders: 
                Authorization: `Bearer $authservice.setToken()`
              
            )
            return next.handle(tokenreq)
          
          else return throwError(error)
        )
      );
    
  

问题是在我收到过期令牌消息后authservice.renewToken()authservice.setToken() 被同时调用,所以再次设置了过期的令牌。

另一个问题是,如果用户使用 cookie 中过期的令牌再次打开应用程序,所有GET 方法都会抛出错误并多次请求新令牌。如何处理过期令牌错误?

【问题讨论】:

Renewtoken 和 settoken 是同步方法吧? 我想是的。我还没有声明它们是异步的 你能显示renewToken的代码吗? 你能用github.com/softonic/axios-retry吗? renewToken 方法到底是做什么的? 【参考方案1】:

您需要传递新令牌。

return next.handle(request).pipe(
        map((event: HttpEvent<any>) => 
            return this.eventCallSendBack(event);
        ),
        catchError((error: HttpErrorResponse) => 
            if (error instanceof HttpErrorResponse) 
                switch (error.status) 
                    case 401:
                        return this.handle401Error(request, next);
                    case 403:
                        //DO Want you want here.
                        throw error;
                    default:
                        throw error;
                
            
            throw error;
        )
    );

这是回调函数。

private eventCallSendBack(event) 
    if (event instanceof HttpResponse) 
        return event.clone( body: event.body );
    
    return event;

您想拥有并添加令牌 fn:

private addToken(event: HttpRequest<any>, token: string) 
    return event.clone(
        setHeaders: 
            Authorization: `Bearer $token`
        
    );

现在假设您的令牌已过期,您需要创建 fn 以从某个 Auth 中获取新令牌。

private handle401Error(
    request: HttpRequest<any>,
    next: HttpHandler
): Observable<any> 
        return authServices
            .refreshToken(
                token //This is the old token to get the new.
            )
            .pipe(
                map(jwt => 
                    return jwt;
                ),
                switchMap((jwt: any) => 
                    return next
                        .handle(this.addToken(request, jwt.token)) // Add token in responce 
                        .pipe(
                            map((event: HttpEvent<any>) => 
                                return this.eventCallSendBack(event);
                            )
                        );
                ),
                catchError(err => 
                    return throwError(err);
                )
            );
    

【讨论】:

【参考方案2】:

您可以通过使用retryWhen 运算符将setToken 连接到返回的observable 来修复此错误行为。 这样renewTokensetToken 将不会并行执行,更重要的是setToken 将被每个请求中的拦截器链考虑在内。

intercept(req: HttpRequest<any>, next: HttpHandler) 
    const authservice = this.injector.get(AuthService);
      
    return of(req).pipe(
          switchMap((req) => 
            return authservice.setToken()               // Gets the token. *should rename this method to getToken()
                .pipe(
                    map(token =>                       // Set the token.
                        const tokenreq = req.clone(
                            setHeaders: 
                                Authorization: `Bearer $authservice.setToken()`
                            
                            );

                        return tokenreq;
                    )
                ),
            switchMap(tokenreq => next.handle(tokenreq)), // Execute next interceptor and eventually send the request.
            retryWhen(errors => errors.pipe(
                mergeMap((err: HttpErrorResponse, i: number) => 
                    authservice.invalidateToken()        // Invalidate token. Erase token or expires it.
                    if (error.error.message == 'The incoming token has expired') 
                        return of(err);                   // will start the current pipe all over again - and get the token once again.
                    

                    return throwError(error);
                )
            )
        )
  

说明:

在问题中,setToken 没有连接到拦截器方法返回的可观察链。 之前的代码执行流程:

1) interceptor method execution -> setToken() -> return observable
2) request asked by http.get(..) -> chain of observables return by interceptors -> request sent -> chain of observables

在这个答案中:

1) interceptor method execution -> setToken() -> return observable
2) request asked by http.get(..) -> chain of observables return by interceptors and setToken() inside one! -> request sent -> chain of observables

注意:

setToken 方法应该返回一个带有令牌的 observable,invalidateToken 应该能够删除令牌。

这可以通过以下方式轻松实现:

private token$: AsyncSubject<string>;

getToken(): Observable 
    if (!token$) this.token$ = new AsyncSubject();

    getTokenOperation.subscribe(t => 
        this.token$.next(t);
        this.token$.complete();
    )

    return this.token$.asObservable();


invalidateToken() 
    this.token$ = null;

【讨论】:

以上是关于在发出克隆请求之前更新令牌的主要内容,如果未能解决你的问题,请参考以下文章

以管理员身份使用自定义令牌向 FB DB 发出 REST 请求

如何使用操作查询发出多个 Alamofire 请求

使用过期令牌发出同时 API 请求时如何避免多个令牌刷新请求

在发出拉取请求之前,是不是有任何理由将功能分支合并到分叉主控中?

如何使用解析会话令牌通过 graphql 发出请求

在发出 OAuth 令牌交换请求时,如何进行深层链接并导航到“LogInLoading”屏幕?