【发布时间】:2019-10-22 15:16:55
【问题描述】:
我第一次使用角度拦截器,我几乎拥有了我想要的东西,但是即使在谷歌上搜索了一段时间后我也无法完全弄清楚。我在本地存储刷新令牌,访问令牌每 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 也很陌生,如果对其他人来说很明显,我很抱歉)。预感我想知道是不是 observable 搞砸了,它不喜欢等待 observable 返回所以我尝试了这个:
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 服务的 observable 的最初原因是为了防止它需要刷新。基本上,身份验证服务会查看它的当前令牌并查看它是否已过期。如果不是,它只会返回of(token),但如果它已过期,它将通过可观察到的 http 帖子返回到服务器,因此只要服务器响应,字符串就会到达。
所以我想我的问题有两个:
- 任何人都可以确认或反驳我对可观察到的干扰拦截器是正确的吗?这似乎是问题所在,但想确定一下。
- 如何在后台为他们刷新令牌,而不必每 15 分钟重新登录一次?
编辑
这里是auth token方法中的逻辑:
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'))
);
}
编辑
好的,在下面的答案以及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.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);
})
);
}
所以基本上我的第一次尝试并不是很遥远,“秘密”是使用管道和合并地图而不是尝试订阅。
【问题讨论】:
-
您是否将 refresh 逻辑封装在一个函数中?如果你,你能添加它的签名吗?
-
任何人都可以确认或反驳我关于可观察到的干扰拦截器是正确的吗?很可能。一般来说,订阅内部服务或 http 拦截器是一种容易出错的做法。这可以通过使用正确的 rxjs 运算符来避免。
-
在您的第一个代码 sn-p 中,如果出现身份验证请求,您将返回
HttpEvent而不是Observable<HttpEvent<any>>。因此,尝试将第一个返回值包装在of()发射器 (learnrxjs.io/operators/creation/of.html) 中,所以它应该看起来像return of(next.handle(req));(ONLY IN AUTH REQUEST) -
@Jota.Toledo 好的,我想这是有道理的,因为 observable 将返回并在单独的时间继续,因此拦截器将无法立即返回结果?所以这一定是一件很常见的事情......那么你如何处理自动令牌刷新呢?我已经发布了我的令牌获取和刷新方法的代码。
-
我的错,我回滚了更改:)。猜猜我的方法也缺少这种情况。
标签: angular authentication angular-http-interceptors