Трудность реализации перехватчика для обработки токенов обновления - PullRequest
0 голосов
/ 01 мая 2018

Я создаю приложение Angular, которое требует аутентификации. Это достигается с помощью жетонов. Жетоны имеют короткий срок службы и должны регулярно обновляться. Я сослался на этот вопрос , но я чувствую, что мои потребности и структура кода различны (например, я не собираюсь «держать» класс, и у меня есть отдельные перехватчики).

Основная цель одного перехватчика - добавить токен в заголовок:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) {

    }

    intercept(req: HttpRequest<any>, next: HttpHandler):
        Observable<HttpEvent<any>> {
        const authToken = this.authService.getToken();
        const refreshToken = this.authService.getRefreshToken();

        // If refresh token is expired, the user will need to relogin
        if(refreshToken == null || this.authService.checkTokenExpired(true)) {
            this.authService.logout();
            return next.handle(req);
        }

        // There is no token to send, continue.
        if (authToken == null || this.authService.checkTokenExpired()) {
            return next.handle(req);
        }   

        const authReq = req.clone({ setHeaders: { Authorization: "Bearer " + authToken } });    
        return next.handle(authReq);

    }

}

У меня есть отдельный перехватчик для обработки ошибки "401":

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService) {

    }

    intercept(req: HttpRequest<any>, next: HttpHandler):
        Observable<HttpEvent<any>> {
        const cBack = tap(res => {
            console.log("Refreshed");
            console.log("Token is " + this.authService.getToken());
            next.handle(req.clone
                ({ setHeaders: { Authorization: "Bearer " + this.authService.getToken() } }));
        })
        return next.handle(req)
            .catch((err: HttpErrorResponse) => {
                if (err.status == 401) {
                    this.authService.refresh()
                        .pipe(cBack).subscribe();

                }
                console.log(err);
                return _throw(err.message);
            });

    };

}

Где AuthService является следующим:

@Injectable()
export class AuthService {
  private authURL: string = "http://localhost:8090/oauth/token";
  private loginPath : string = "/login";
  isLoggedIn: boolean = false;
  redirectURL: string;

  constructor(private http: HttpClient) {
    this.redirectURL = "";
    this.isLoggedIn = this.getToken() != null;
  }
  login(user: string, password: string): Observable<boolean> {
    var data = new FormData();
    data.append("grant_type", "password");
    data.append("username", user);
    data.append("password ", password);

    const httpOptions = {
      headers: new HttpHeaders({
        'Authorization': 'Basic ' + window.btoa("web:secret")
      })
    };

    return this.http.post<AuthResponseModel>(this.authURL, data, httpOptions)
      .pipe(
        map((r: AuthResponseModel) => {
          if (r.access_token) {
            localStorage.setItem("access_token", r.access_token);
            localStorage.setItem("refresh_token", r.refresh_token);
            this.isLoggedIn = true;
            return true;
          } 
        }
        ));
  };

  refresh() : Observable<boolean> {
    var data = new FormData();
    data.append("grant_type", "refresh_token");
    data.append("refresh_token", this.getRefreshToken());
    const httpOptions = {
      headers: new HttpHeaders({
        'Authorization': 'Basic ' + window.btoa("web:secret")
      })
    };

   return this.http.post<AuthResponseModel>(this.authURL, data, httpOptions)
    .pipe(
     map( r => {
        localStorage.setItem("access_token", r.access_token);
        localStorage.setItem("refresh_token", r.refresh_token);
        this.isLoggedIn = true;
        return true;
      })
    )

  }

  logout(): void {
    localStorage.removeItem("access_token");
    localStorage.removeItem("refresh_token");
    this.isLoggedIn = false;
  }

  checkTokenExpired(checkRefresh = false) : boolean {
    if(checkRefresh) {  return decode(this.getRefreshToken()).exp < (Date.now().valueOf() / 1000); }
    return decode(this.getToken()).exp < (Date.now().valueOf() / 1000);
  }
  getToken(): string {
    return localStorage.getItem("access_token");
  }

  getRefreshToken() : string {
    return localStorage.getItem('refresh_token');
  }


}

Когда приложение получает 401, оно правильно отправляет запрос на обновление токена. Я получаю сообщения "Refreshed" и "Token is ..." console.log от перехватчика регистрации. Кроме того, он помещает токен в локальное хранилище, как и должно быть. Если я вручную обновлю страницу, это на самом деле работает. Однако моей конечной целью является, когда приложение получает 401, обновить токен и повторно отправить исходный запрос. Последний шаг - это поведение, которое в данный момент не работает.

Буду признателен за любую помощь. Спасибо.

EDIT:

Версия с повторной попыткой.

  let interceptor  = this;

return next.handle(req.clone({ setHeaders: { Authorization: "Bearer " + this.authService.getToken() } })) 
.pipe(
    retryWhen(error$ => {
       return error$.pipe(
         mergeMap(error => {
           if (error.status === 401) {                  
             return interceptor.authService.refresh();
           } else {
             // re-throw
             _throw(error)
           }
         })
       );
    })        
 );

1 Ответ

0 голосов
/ 01 мая 2018

Не совсем уверен, зачем вам нужен перехватчик Auth и Logging, но я думаю, вы можете сделать что-то вроде этого:

class Interceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  return next
    .handle(req)
    .pipe(
       retryWhen(error$ => {
          return error$.pipe(
            mergeMap(error => {
              if (error.status === 401) {
                // fetch new token + retry
                return this.someService.refreshToken();
              } else {
                // re-throw
                _throw(error)
              }
            })
          );
       })        
    );
} 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...