我正在使用Angular的Http
分段,但我决定进行迁移并使用新的HttpClient
,我正在尝试使用Interceptors
来创建解决方案来管理当我需要刷新令牌以及何时需要修改标头以放置授权令牌时的情况。
答案 0 :(得分:3)
首先我找到了这些帖子以及其他许多帖子:
...但如果您只想处理授权标题的操作,那么这些解决方案是完美的。然后我想出了这个解决方案
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private authService: Auth) {
}
private getRequestWithAuthentication(request: HttpRequest<any>, next: HttpHandler, auth: OAuthService): Observable<HttpEvent<any>> {
const req = request.clone({
headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
});
return next.handle(req);
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// To avoid cyclic dependency
const auth = this.injector.get(OAuthService);
if (auth.hasAuthorization()) {
return this.getRequestWithAuthentication(request, next, auth);
} else if (auth.hasAuthorizationRefresh() && request.url !== AUTHORIZE_URL) {
return auth.refreshToken().flatMap(
(res: any) => {
auth.saveTokens(res);
return this.getRequestWithAuthentication(request, next, auth);
}
).catch(() => {
return next.handle(request);
});
} else if (request.url === AUTHORIZE_URL) {
return next.handle(request);
}
return this.getRequestWithAuthentication(request, next, auth);
}
}
这个主要想法很简单:
HttpClient
中向OAuthService
发出了请求,因此它也会从拦截器传来而且它会让如果我不检查就无限循环。此解决方案在某些情况下运行正常,但事情是,例如令牌已过期并且您有多个请求,每个请求都会尝试刷新令牌。
在此之后我发现了这个解决方案,但我想知道你对我所做的代码和方式的看法。
好的,首先我创建了一个服务来保存刷新令牌请求的状态和Observable来知道请求何时完成。
@Injectable()
export class RefreshTokenService {
public processing: boolean = false;
public storage: Subject<any> = new Subject<any>();
public publish(value: any) {
this.storage.next(value);
}
}
我注意到,如果我有两个Interceptor来刷新令牌并处理它并且一个用于放置授权头(如果存在),那就更好了。
@Injectable()
export class RefreshTokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector, private tokenService: RefreshTokenService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
if (!auth.hasAuthorization() && auth.hasAuthorizationRefresh() && !this.tokenService.processing && request.url !== AUTHORIZE_URL) {
this.tokenService.processing = true;
return auth.refreshToken().flatMap(
(res: any) => {
auth.saveTokens(res);
this.tokenService.publish(res);
this.tokenService.processing = false;
return next.handle(request);
}
).catch(() => {
this.tokenService.publish({});
this.tokenService.processing = false;
return next.handle(request);
});
} else if (request.url === AUTHORIZE_URL) {
return next.handle(request);
}
if (this.tokenService.processing) {
return this.tokenService.storage.flatMap(
() => {
return next.handle(request);
}
);
} else {
return next.handle(request);
}
}
}
所以我在这里等待刷新令牌可用或失败,然后我发布需要授权标题的请求。
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private injector: Injector) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const auth = this.injector.get(OAuthService);
let req = request;
if (auth.hasAuthorization()) {
req = request.clone({
headers: request.headers.set('Authorization', auth.getHeaderAuthorization())
});
}
return next.handle(req).do(
() => {},
(error: any) => {
if (error instanceof HttpErrorResponse) {
if (error.status === 401) {
auth.logOut();
}
}
});
}
}
@NgModule({
imports: [
...,
HttpClientModule
],
declarations: [
...
],
providers: [
...
OAuthService,
AuthService,
RefreshTokenService,
{
provide: HTTP_INTERCEPTORS,
useClass: RefreshTokenInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true
}
],
bootstrap: [AppComponent]
})
export class AppModule {
}
请欢迎任何反馈,如果我发错了,请告诉我。我使用 Angular 4.4.6进行测试,但我不知道它是否适用于角度5,我认为应该可行。
答案 1 :(得分:1)
对于在Angular 4中寻找解决方案的任何人(可能是Angular 5+需要稍作修改),我想出了以下解决方案:
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
private _refreshRequest: Observable<ApiResult<any>> | null = null;
constructor(
private _router: Router,
private _tokenStorage: TokenStorageService,
private _injector: Injector) {
}
private _addTokenHeader(request: HttpRequest<any>) {
const authToken = this._tokenStorage.authToken;
if (!authToken) {
return request;
}
return request.clone({setHeaders: {'Authorization': 'Bearer ' + authToken.value}});
}
private _fail() {
this._tokenStorage.clearTokens();
this._router.navigate(['/login']);
return throwError(new HttpErrorResponse({status: 401}));
}
private _refreshAuthToken(request: HttpRequest<any>, next: HttpHandler) {
// AuthService has the following dependency chain:
// ApiService -> HttpClient -> HTTP_INTERCEPTORS
// If injected at the constructor this causes a circular dependency error.
const authService = <AuthService>this._injector.get(AuthService);
if (this._refreshRequest === null) {
// Send the auth token refresh request
this._refreshRequest = authService.refreshAuthToken();
this._refreshRequest.subscribe(() => this._refreshRequest = null);
}
// Wait for the auth token refresh request to finish before sending the pending request
return this._refreshRequest
.flatMap(result => {
if (result.success) {
// Auth token was refreshed, continue with pending request
return this._sendRequest(this._addTokenHeader(request), next);
}
// Refreshing the auth token failed, fail the pending request
return this._fail();
});
}
private _sendRequest(request: HttpRequest<any>, next: HttpHandler) {
return next.handle(request).catch((err: HttpErrorResponse, caught) => {
// Send the user to the login page If there are any 'Unauthorized' responses
if (err.status === 401) {
this._router.navigate(['/login']);
}
return Observable.throw(err);
});
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.url.indexOf('/api/auth') !== -1) {
// This interceptor should not be applied to auth requests
return this._sendRequest(request, next);
}
const authToken = this._tokenStorage.authToken;
const refreshToken = this._tokenStorage.refreshToken;
// Attempt to refresh the auth token if it is expired or about to expire
if (authToken && authToken.expiresWithinSeconds(60)) {
if (refreshToken && !refreshToken.isExpired) {
return this._refreshAuthToken(request, next);
}
else {
// Auth token has expired and cannot be refreshed
return this._fail();
}
}
return this._sendRequest(this._addTokenHeader(request), next);
}
}
如果当前的身份验证令牌已过期,这将向服务器发出身份验证令牌刷新请求,但是有一个有效的刷新令牌。进一步的请求被缓冲,直到挂起的刷新请求完成。
未显示的来源是:
- TokenStorageService
只使用localStorage
- Jwt
类包装令牌并使令牌声明像到期日期易于访问
- ApiResult
这只是我的应用程序HttpResponse
的简单包装器,与此处的任何内容都没有特别相关
编辑: Angular 6/7
import { Injectable, Inject, Injector } from '@angular/core';
import { Router } from '@angular/router';
import {
HttpEvent,
HttpInterceptor,
HttpHandler,
HttpRequest,
HttpErrorResponse,
} from '@angular/common/http';
import { Observable, of, throwError } from 'rxjs';
import { catchError, flatMap } from 'rxjs/operators';
import { ApiResult } from '../../api';
import { TokenStorageService } from './token-storage.service';
import { AuthService } from './auth.service';
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
private _refreshRequest: Observable<ApiResult> | null = null;
constructor(
private _router: Router,
private _tokenStorage: TokenStorageService,
@Inject('BASE_URL') private _baseUrl: string,
private _injector: Injector) {
}
private _addTokenHeader(request: HttpRequest<any>) {
const authToken = this._tokenStorage.authToken;
if (!authToken) {
return request;
}
return request.clone({setHeaders: {'Authorization': 'Bearer ' + authToken.value}});
}
private _forceLogin() {
this._tokenStorage.clearTokens();
this._router.navigate(['/account/login'], { queryParams: {
message: 'Your session has expired. Please re-enter your credentials.'
}});
}
private _fail() {
this._forceLogin();
return throwError(new HttpErrorResponse({status: 401}));
}
private _refreshAuthToken(request: HttpRequest<any>, next: HttpHandler) {
// AuthService has the following dependency chain:
// ApiService -> HttpClient -> HTTP_INTERCEPTORS
// If injected at the constructor this causes a circular dependency error.
const authService = <AuthService>this._injector.get(AuthService);
if (this._refreshRequest === null) {
// Send the auth token refresh request
this._refreshRequest = authService.refreshAuthToken();
this._refreshRequest.subscribe(() => this._refreshRequest = null);
}
// Wait for the auth token refresh request to finish before sending the pending request
return this._refreshRequest.pipe(flatMap(result => {
if (result.success) {
// Auth token was refreshed, continue with pending request
return this._sendRequest(this._addTokenHeader(request), next);
}
// Refreshing the auth token failed, fail the pending request
return this._fail();
}));
}
private _sendRequest(request: HttpRequest<any>, next: HttpHandler) {
return next.handle(request).pipe(catchError(err => {
// Send the user to the login page If there are any 'Unauthorized' responses
if (err.status === 401) {
this._forceLogin();
}
return throwError(err);
}));
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (request.url.indexOf(this._baseUrl) === -1 || request.url.indexOf('/api/auth') !== -1) {
// This interceptor should not be applied to non-api requests or auth requests
return this._sendRequest(request, next);
}
const authToken = this._tokenStorage.authToken;
const refreshToken = this._tokenStorage.refreshToken;
// Attempt to refresh the auth token if it is expired or about to expire
if (authToken && authToken.expiresWithinSeconds(60)) {
if (refreshToken && !refreshToken.isExpired) {
return this._refreshAuthToken(request, next);
}
else {
// Auth token has expired and cannot be refreshed
return this._fail();
}
}
return this._sendRequest(this._addTokenHeader(request), next);
}
}