确保使用刷新令牌的路由

时间:2018-09-05 10:13:20

标签: angular

路由配置正在使用isAuthenticated服务方法:

canActivate(route: ActivatedRouteSnapshot): boolean {
  const expectedRole = route.data.expectedRole ? route.data.expectedRole : null;
  const tokenPayload = this.authService.getDecodedAccessToken();
  const role = tokenPayload.role ? tokenPayload.role : null;
  if (!this.authService.isAuthenticated()) {
    this.router.navigate(['login']);
    return false;
  } else if (role != null && role !== expectedRole) {
    this.router.navigate(['login']);
    return false;
  } else {
    return true;
  }
}

此方法正在检查浏览器本地存储中的访问令牌有效性,而尚未尝试使用刷新令牌:

public isAuthenticated(): boolean {
  const token = this.getAccessTokenFromLocalStorage();
  return (token && !this.jwtHelperService.isTokenExpired(token));
}

我想知道如何使用刷新令牌。

我希望我的拦截器能够完成这项工作:

return this.refreshToken()
.pipe(
  switchMap(() => {
    request = this.addAccessToken(request);
    return next.handle(request);
  })
)
.pipe(
  catchError(
    (refreshError) => {
      this.authService.logout();
      return empty();
      // return throwError(refreshError); TODO
    })
);

然后在请求中发送刷新令牌:

private refreshToken() {
  if (this.refreshTokenInProgress) {
    return new Observable(observer => {
      this.tokenRefreshed$.subscribe(() => {
        observer.next();
        observer.complete();
      });
    });
  } else {
    this.refreshTokenInProgress = true;
    console.log('Sending a refresh token request...');
    return this.authService.refreshAccessToken()
      .pipe(
        tap(() => {
          console.log('The refresh token has been received');
          this.refreshTokenInProgress = false;
          this.tokenRefreshedSource.next();
        })
      );
  }
}

然后将更新的访问令牌添加到下一个请求:

private addAccessToken(request): HttpRequest<any> {
  if (!this.tokenService.getAccessTokenFromLocalStorage()) {
    return request;
  }

  // The original request is immutable and cannot be changed
  return this.authService.addAccessTokenToClonedRequest(request);
}

但是现在,我的isAuthenticated方法完全忽略了这一点。

我应该修改isAuthenticated方法以使其称为refreshToken方法吗?还是有办法将拦截器插入路由配置?

更新:

我修改了isAuthenticated方法:

public isAuthenticated(): boolean {
  let isAuthenticated = false;
  if (this.tokenService.accessTokenIsNotExpired()) {
    isAuthenticated = true;
  } else {
    if (this.tokenService.refreshTokenIsNotExpired()) {
      this.refreshAccessToken()
      .pipe(
        map(() => {
          console.log('The access token has been refreshed');
          // TODO How to resend this unauthorized request ?
        })
      );
    }
  }
  return isAuthenticated;
}

但是刷新令牌响应是异步的,而canActivate属性是同步的。所以我想我在上面的更新方法中丢失了未经授权的请求。有什么办法可以重新发送此未经授权的请求?

还有,如何处理我漂亮的拦截器?访问令牌刷新部分是否仍不使用它?更新:我现在可以回答一个问题:当客户端在isAuthenticated方法中查找访问令牌时,当访问令牌仍然有效时,将使用拦截器访问令牌刷新,但是在请求到达时不再有效到REST令牌刷新端点,服务器将检查令牌。我会这么说。

更新: 我也尝试过这种方法,但没有帮助:

public isAuthenticated(): boolean {
  let isAuthenticated = true;
  if (this.tokenService.accessTokenExpired()) {
    isAuthenticated = false;
    if (this.tokenService.refreshTokenExpired()) {
      isAuthenticated = false;
    } else {
      this.refreshAccessToken()
        .pipe(
          map((response: HttpResponse<any>) => {
            console.log('The access token has been refreshed');
          }),
          catchError((error, caught) => {
            console.log('The access token has not been refresh');
            console.log(error);
            return empty();
          })
        );
    }
  }
  return isAuthenticated;
}

UPDATE:现在可以正常工作了,刷新令牌确实可以更新访问令牌。而且路由行为符合预期。

我更改了canActivate方法,因为它实际上也可以使用Observable

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
  const expectedRole = route.data.expectedRole ? route.data.expectedRole : null;
  const tokenPayload = this.tokenService.getDecodedAccessToken();
  return this.authService.isAuthenticated()
  .pipe(
    map(isAuth => {
      console.log('A response was returned');
      console.log(isAuth);
      if (!isAuth) {
        this.router.navigate(['login']);
        return false;
      } else {
        return true;
      }
    }),
    catchError((error, caught) => {
      console.log('An error was returned');
      console.log(error);
      return of(false);
    })
  );
}

使用isAuthenticated方法现在看起来像:

public isAuthenticated(): Observable<boolean> {
  if (this.tokenService.accessTokenExpired()) {
    console.log('The access token expired.');
    if (this.tokenService.refreshTokenExpired()) {
      console.log('The refresh token expired.');
      return of(false);
    } else {
      return this.refreshAccessToken()
      .pipe(
        map(response => {
          if (response) {
            console.log('The access token has been refreshed');
            return true;
          }
        }),
        catchError((error, caught) => {
          console.log('The access token could not be refreshed');
          console.log(error);
          return of(false);
        })
      );
    }
  }
  return of(true);
}

public refreshAccessToken(): Observable<any> {
  console.log('Sending the refresh token to obtain a new access token');
  let httpHeaders: HttpHeaders = this.httpService.buildHeader(null);
  httpHeaders = this.addRefreshTokenHeader(httpHeaders);
  httpHeaders = this.addClientIdHeader(httpHeaders);

  return this.httpService.postWithHeadersInResponse(URI_REFRESH_TOKEN, {}, httpHeaders)
    .pipe(
      map((response: HttpResponse<any>) => {
        // Only the access token is refreshed
        // Refresing the refresh token would be like giving a never expiring refresh token
        this.storeAccessTokenInLocalStorage(response);
        console.log('Stored the refreshed access token in the local storage');
        return true;
      })
    );
}

2 个答案:

答案 0 :(得分:1)

为避免在每个HTTP请求上刷新令牌,我使用了rxjs Scheduler在到期前刷新会话。

不刷新每个请求。用户可能正在使用该网页(可能正在编写消息),但没有发送请求,因此该用户处于活动状态,但会话仍将终止。

不信任JWT中的到期时间字段来计算何时刷新令牌,因为服务器的时间可能与客户端的时间不同。如果需要,请调整计算中的时差。

此外,使用刷新令牌时,请记住设置刷新的时间限制。否则,通过反复刷新,用户可能会遇到无限会话。

这些是我面临的问题。客户端在闲置8分钟后请求会话期满。

答案 1 :(得分:0)

我认为你是对的。

在我的情况下,刷新令牌是JWT,在您的情况下,我将刷新令牌和访问令牌都保存在本地存储中。

在路由保护程序中,我查看刷新令牌是否已过期,并且我正在使用Auth0中的angular2-jwt软件包。

此外,如果打开的页面不会调用任何http请求,则应该执行一个http请求以检查访问令牌是否已过期并刷新它。您也可以警惕,这取决于应用程序。