角拦截器添加令牌并自动刷新 [英] Angular Interceptor to add Token and Automatically Refresh

查看:54
本文介绍了角拦截器添加令牌并自动刷新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是第一次使用角度拦截器,我几乎拥有了我想要的东西,但是即使在谷歌搜索了一会儿之后,我仍然无法弄清楚某些东西.我在本地存储刷新令牌,访问令牌每15分钟失效一次;我希望能够使用刷新令牌在到期时自动刷新其身份验证令牌.

I'm working with angular interceptors for the first time and I almost have what I want but there is something I can't quite figure out even after googling around for a while. I am storing a refresh token locally and the access token expires every 15 minutes; I want to be able to use the refresh token to automatically refresh their auth token when it expires.

我的第一次尝试是这样的:

My first attempt went like this:

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也很陌生,所以如果对其他人来说很明显,对不起.)我直觉地想知道这是否是可观察的混乱,它不喜欢等待可观察的物体返回,所以我尝试了这个:

This did not seem to work and I couldn't figure out why (I'm new to Angular and fairly new to JS as well so sorry if it's obvious to others). On a hunch I wondered if it was the observable messing things up and it doesn't like waiting for the observable to return so I tried this:

  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帖子返回到服务器,因此只要服务器响应,字符串就将到达.

And now it seems to work! That suggests that I may have been correct in my hunch (or that it's something else inside the other code that I'm not seeing). Anyway, working is good but this leaves me with the question of how to refresh. The original reason I had it using an observable from the auth service was in case it needed to refresh. Basically the auth service would look at it's current token and see if it was expired or not. If not it would just return of(token) but if it was expired it would reach back out to the server via an http post which is observable, so the string would arrive whenever the server responded.

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

So I guess my question is two-fold:

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

编辑

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

Here is the logic in the auth token method:

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'))
  );
}

编辑

好吧,在以下答案以及的帮助下,这个问题是我想出的:

Ok so with help from the answer below as well as this question here is what I came up with:

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.clone());
}

return this.authService.GetCurrentToken().pipe(
  mergeMap((token: string) => {
    if (token === null || token === '') {
      throw new Error('Refresh failed to get token');
    } else {
      return next.handle(req.clone({setHeaders: {Authorization: `Bearer ${token}`}}));
    }
  }),
  catchError((err: HttpErrorResponse) => {
    if (err.status === 401) {
      this.router.navigateByUrl('/login');
    }
    return throwError(err);
  })
);
}

所以从根本上说,我的第一次尝试并非遥不可及,秘密"是使用管道和合并映射而不是尝试订阅.

So basically it ended up that my first attempt was not terribly far off, the 'secret' was to use a pipe and merge map instead of trying to subscribe.

推荐答案

您可以尝试以下方法.我可能夸大了其中的FP含量:

You could try with an approach as follows. I might have exaggerated with the amount of FP in it:

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;

这篇关于角拦截器添加令牌并自动刷新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆