Угловой перехватчик для добавления токена и автоматического обновления - PullRequest
0 голосов
/ 07 июня 2019

Я впервые работаю с угловыми перехватчиками, и у меня почти есть то, что я хочу, но есть кое-что, что я не могу понять даже после того, как некоторое время гуглюсь. Я храню токен обновления локально, и токен доступа истекает каждые 15 минут; Я хочу использовать токен обновления для автоматического обновления их токена аутентификации по истечении срока действия.

Моя первая попытка прошла так:

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.toLowerCase().includes('/auth')) {
  // It's an auth request, don't get a token
  return next.handle(req);
}

// Not an auth endpoint, should have a token
this.authService.GetCurrentToken().subscribe(token => {
  // Make sure we got something
  if (token == null || token === '') {
    return next.handle(req);
  }

  // Have a token, add it
  const request = req.clone({
    setHeaders: {
      Authorization: `Bearer ${token}`
    }
  });

  return next.handle(request);
});
}

Похоже, это не сработало, и я не мог понять почему (я новичок в Angular и довольно новичок в JS, так что извините, если это очевидно для других). На догадке я подумал, не было ли это наблюдаемой путаницей, и ему не нравится ждать, пока наблюдаемое вернется, поэтому я попробовал это:

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.toLowerCase().includes('/auth')) {
  // It's an auth request, don't get a token
  return next.handle(req);
}

const token = this.authService.GetAccessTokenWithoutRefresh();
const request = req.clone({
  setHeaders: {
    Authorization: `Bearer ${token}`
  }
});
return next.handle(request);   
}

А теперь похоже на работу! Это говорит о том, что я мог быть прав в своей догадке (или что это что-то еще внутри другого кода, который я не вижу). В любом случае, работа - это хорошо, но у меня остается вопрос, как освежиться. Первоначальная причина, по которой я использовал это с помощью наблюдаемой службы аутентификации, заключалась в том, что нужно было обновить. По сути, служба аутентификации проверяет свой текущий токен и определяет, истек ли он или нет. Если нет, он просто вернет of(token), но если срок его действия истечет, он обратится к серверу через сообщение http, которое можно наблюдать, поэтому строка будет поступать всякий раз, когда сервер отвечает.

Так что я думаю, мой вопрос двоякий:

  1. Может ли кто-нибудь подтвердить или опровергнуть, что я был прав насчет наблюдаемой путаницы с перехватчиком? Кажется, что это проблема, но хотелось бы быть уверенным.
  2. Как мне справиться с обновлением токена для них в фоновом режиме без необходимости повторного входа каждые 15 минут?

EDIT

Вот логика в методе аутентификации:

GetCurrentToken(): Observable<string> {
if (this.AccessToken == null) {
  return null;
}
if (this.Expiry > new Date()) {
  return of(this.AccessToken);
}

// Need to refresh
return this.RefreshToken().pipe(
  map<LoginResult, string>(result => {
    return result.Success ? result.AccessToken : null;
  })
);
}

и метод обновления:

private RefreshToken(): Observable<LoginResult> {
const refreshToken = localStorage.getItem('rt');
if (refreshToken == null || refreshToken === '') {
  const result = new LoginResult();
  // Set other stuff on result object
  return of(result);
}

const refresh = new RefreshTokenDto();
refresh.MachineId = 'WebPortal';
refresh.TokenId = refreshToken;
return this.http.post(ApiData.baseUrl + '/auth/refresh', refresh)
  .pipe(
    tap<AuthResultDto>(authObject => {
      this.SetLocalData(authObject);
    }),
    map<AuthResultDto, LoginResult>(authObject => {
      const result = new LoginResult();
      // Set other stuff on the result object
      return result;
    }),
    catchError(this.handleError<LoginResult>('Refresh'))
  );
}

1 Ответ

0 голосов
/ 07 июня 2019

Вы можете попробовать подход следующим образом.Я мог бы преувеличить с количеством FP ​​в этом:

export class AuthInterceptor {
 ctor(private authService: AuthService){}
 intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

   return of(req.url.toLowerCase().includes('/auth')).pipe(
     mergeMap(isAuthRequest => !isAuthRequest
       // Missing: handle error when accessing the access token
       ? this.authService.accessToken$.pipe(map(addAuthHeader(req)))
       : of(req)
     ),
     mergeMap(nextReq => next.handle(nextReq))
   );
 }
}

function addAuthHeader(req: HttpRequest<any>): (token:string)=> HttpRequest<any> {
  return token => req.clone({setHeaders: {Authorization: `Bearer ${token}`}})
} 

И служба аутентификации:

export class AuthService {
  ctor(private http: HttpClient){}

  get accessToken$(): Observable<string> {
    return of(this.AccessToken).pipe(
       mergeMap(token => token === null
         ? throwError("Access token is missing")
         : of(this.Expiry > new Date())
       ),
       mergeMap(accessTokenValid => accessTokenValid
         ? of(this.AccessToken)
         : this.refreshToken()
       )
    );
  }

  refreshToken(): Observable<string> {
    return of(localStorage.getItem('rt')).pipe(
      mergeMap(refreshToken => !refreshToken 
        ? of(extractAccessTokenFromLogin(createLoginResult())
        : this.requestAccessToken(this.createRefreshToken(refreshToken))
      )
    );
  }

  private requestAccessToken(refreshToken: RefreshTokenDto): Observable<string> {
    return this.http.post<AuthResultDto>(ApiData.baseUrl + '/auth/refresh', refreshToken)
     .pipe(
       tap(auth => this.SetLocalData(auth )),
       map(auth => this.mapAuthObjToLoginRes(auth)),
       map(extractAccessTokenFromLogin)
       catchError(this.handleError<string>('Refresh'))
     )
  }

  private createRefreshToken(tokenId: string): RefreshTokenDto{...}

  private createLoginRes(): LoginResult {...}

  private mapAuthObjToLoginRes(val: AuthResultDto): LoginResult{...}
}

function extractAccessTokenFromLogin(login: LoginResult): string 
     => login.Success ? login.AccessToken : null;
...