角拦截器添加令牌并自动刷新

时间:2019-06-07 06:10:53

标签: angular authentication angular-http-interceptors

我是第一次使用角度拦截器,我几乎拥有了我想要的东西,但是即使在谷歌搜索了一会儿之后,我仍然无法弄清某些东西。我在本地存储刷新令牌,访问令牌每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);   
}

现在看来可行!这表明我可能预感到是正确的(或者是我未看到的其他代码中的其他内容)。无论如何,工作还是不错的,但这给我留下了如何刷新的问题。我使用auth服务中的可观察对象的最初原因是如果需要刷新。基本上,身份验证服务将查看它的当前令牌,并查看它是否已过期。如果不是,它将仅返回of(token),但是如果它过期了,它将通过可观察到的http帖子返回到服务器,因此只要服务器响应,字符串就将到达。

所以我想我的问题有两个:

  1. 有人能证实或驳斥我关于可观察到的干扰拦截器的正确信息吗?似乎是问题所在,但想确定。
  2. 我如何处理它们在后台刷新令牌而不必每15分钟重新登录一次?

编辑

这是auth令牌方法中的逻辑:

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 :(得分:0)

您可以尝试以下方法。我可能会夸大其中的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;