如何在 Angular 17 中使用功能拦截器刷新令牌

问题描述 投票:0回答:2

我使用以下基于类的拦截器来刷新用户令牌:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private inject: Injector) {}

    intercept(request: HttpRequest < any > , next: HttpHandler): Observable < HttpEvent < any >> {
        let tokenService = this.inject.get(TokenService);

        let authReq = request;
        authReq = this.AddTokenHeader(request, tokenService.getAccessToken())

        return next.handle(authReq).pipe(
            catchError(errorData => {
                if (errorData.status == 401) {
                    return this.handleRefreshToken(request, next);
                }
                return throwError(errorData);
            })
        );
    }

    AddTokenHeader(request: HttpRequest < any > , token: any) {
        return request.clone({
            headers: request.headers.set('Authorization', 'Bearer ' + token)
        });
    }

    handleRefreshToken(request: HttpRequest < any > , next: HttpHandler) {
        let tokenService = this.inject.get(TokenService);
        let authService = this.inject.get(AuthService);
        let sessionStorageService = this.inject.get(SessionStorageService);

        var refreshTokenVM = new RefreshTokenVM();
        refreshTokenVM.UserId = tokenService.getUserId();
        refreshTokenVM.RefreshToken! = tokenService.getRefreshToken() !;

        return tokenService.refreshToken(refreshTokenVM).pipe(
            switchMap((data: any) => {
                sessionStorageService.saveData('accessToken', data.accessToken);
                sessionStorageService.saveData('refreshToken', data.refreshToken);
                return next.handle(this.AddTokenHeader(request, data.accessToken))
            }),
            catchError(errorData => {
                authService.logout();
                return throwError(errorData);
            })
        );
    }
}

在 Angular 17 中,我们必须使用函数式拦截器。我尝试将上面的代码翻译为功能拦截器:

export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const clonedRequest = request.clone({
    setHeaders: {
      Authorization: 'Bearer ' + 'token',
    }
  });
  return next(clonedRequest).pipe(
    catchError(errorData => {
      if (errorData.status == 401) {

      }
      return throwError(() => errorData);
    })
  );
};

问题是我无法在拦截器函数中注入

tokenService
(我将令牌存储在
SessionStrorage
中)。另外,我不知道如何将
handleRefreshToken
函数集成到功能拦截器中。我该如何解决这个问题?

angular angular-http-interceptors
2个回答
2
投票

您应该能够使用

inject
函数来访问 TokenService

import { inject } from '@angular/core';

export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const tokenService = inject(TokenService); 

  const clonedRequest = request.clone({
  ...

0
投票

关于你的第二个问题(集成刷新令牌功能),我建议你添加一个新的功能拦截器。我将吸收您原始帖子的一部分并添加一些我的观点。完整的代码将如下所示:

export const authInterceptor: HttpInterceptorFn = (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const sessionStorageService = inject(SessionStorageService);
  const { accessToken } = sessionStorageService.getSession(); // will return { accessToken: 'myAccessToken', refreshToken: 'myRefreshToken'}
  if (accessToken) {
    const clonedRequest = request.clone({
       headers: req.headers.set('Authorization', `Bearer ${accessToken}`),
    });
    return next(clonedRequest);
  } else {
    return(request);
  }
};


export const unauthErrorInterceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
  const tokenService = inject(TokenService);
  const authService = inject(AuthService);
  const sessionStorageService = inject(SessionStorageService);
  
  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error instanceof HttpErrorResponse &&
        !(req.url.includes('auth/login') || req.url.includes('auth/refresh')) && // <- this will avoid an infinite loop when the accessToken expires.
        error.status === 401) {
        const { refreshToken } = sessionStorageService.getSession();
        if (refreshToken) {
          return tokenService.refreshToken().pipe(
            switchMap((refreshResult) => {
              // assuming that tokenService.refreshToken() will return { accessToken: 'myNewAccessToken', refreshToken: 'myNewRefreshToken'}
              sessionStorageService.saveSession(refreshResult);
              return next(req.clone({
                headers: req.headers.set('Authorization', `Bearer ${refreshResult.accessToken}`),
              }));
            }),
            catchError((error) => {
              console.log('error')
              if (error.status == '403' || error.status === '401') {
                authService.logout();
              }
              return throwError(() => error);
            })
          );
        }
      }
      authService.logOut();
      return throwError(() => new Error('Unauthorized Exception'));
    })
  );
};

此外,在您的

token.service.ts
中,您将需要更新标头以使用刷新令牌而不是访问令牌:

...
const AUTH_API = `${environment.apiUrl}/auth`;
const httpOptions = {
  headers: new HttpHeaders({ 'Content-Type': 'application/json' })
};
...
refreshToken(): Observable<RefreshResultDto> {
  const { refreshToken } = this.sessionStorageService.getSession();
  return this.httpClient.post<RefreshResultDto>(`${AUTH_API}/refresh`, {}, {
      ...httpOptions,
      headers: httpOptions.headers.set('Authorization', `Bearer ${refreshToken}`)
    });
}

refresh-result.dto.ts

export interface RefreshResultDto {
  accessToken: string;
  refreshToken: string;
}

问候!

© www.soinside.com 2019 - 2024. All rights reserved.