Angular 保护路由以使用刷新令牌

Angular 保护路由以使用刷新令牌,angular,Angular,路线配置正在使用isAuthenticated服务方法: canActivate(route: ActivatedRouteSnapshot): boolean { const expectedRole = route.data.expectedRole ? route.data.expectedRole : null; const tokenPayload = this.authService.getDecodedAccessToken(); const role = tokenP

路线配置正在使用
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);
}
然后将更新的访问令牌添加到下一个请求:

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);
}
但是刷新令牌响应是异步的,而
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;
}
通过
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;
      })
    );
}
public isAuthenticated():可观察{
if(this.tokenService.accessTokenExpired()){
log('访问令牌已过期');
if(this.tokenService.refreshtTokenExpired()){
log('刷新令牌已过期');
归还(假);
}否则{
返回此。refreshAccessToken()
.烟斗(
映射(响应=>{
如果(答复){
log('访问令牌已刷新');
返回true;
}
}),
catchError((错误,捕获)=>{
log('无法刷新访问令牌');
console.log(错误);
归还(假);
})
);
}
}
返回(真);
}
public refreshAccessToken():可观察{
log('发送刷新令牌以获取新的访问令牌');
让httpHeaders:httpHeaders=this.httpService.buildHeader(null);
httpHeaders=this.addRefreshTokenHeader(httpHeaders);
httpHeaders=this.addClientHeader(httpHeaders);
返回此.httpService.postWithHeadersInResponse(URI\u REFRESH\u令牌,{},httpHeaders)
.烟斗(
map((响应:HttpResponse)=>{
//仅刷新访问令牌
//重新设计刷新令牌就像给出一个永不过期的刷新令牌
此.storeAccessTokenInLocalStorage(响应);
log('将刷新的访问令牌存储在本地存储器中');
返回true;
})
);
}
我认为你是对的

在我的例子中,刷新令牌是JWT,与您的例子一样,我将刷新和访问令牌都保存在本地存储中。切勿将其保存在本地存储中。身份验证令牌只能保存在带有httpOnly标志的Cookie中

在route guard中,我观察刷新令牌是否过期,为此我使用Auth0的angular2 jwt包


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

以避免在每次HTTP请求上刷新令牌,我用于在过期之前刷新会话

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

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

另外,在使用刷新令牌时,请记住设置刷新的时间限制。否则,用户可能通过反复刷新而拥有无限会话


这些就是我面临的问题。客户端在8分钟不活动后请求会话过期。

总之,您理解我的问题:-)我计划让访问令牌在一小时后过期,刷新令牌在一周后过期。我遇到的问题是,由于令牌刷新异步进程将
isAuthenticated
设置为false,我得到了未经授权的令牌,将用户重定向到登录页面。那么您没有使用
canActivate
功能?那么你如何确保你的路线安全呢?或者,如果您确实使用了它,那么如何让它与刷新令牌一起更新访问令牌服务器端?我认为您不应该自动刷新令牌。若用户正在观看视频或写书(即使可能是一周),对于类似的不同情况,您应该逐个刷新令牌。