Angular - JWT Refresh Token
很长一段时间后,我没有找到有关刷新令牌的方法
TTL:30分钟
刷新TTL:2周
如果在无效45分钟后刷新页面,那么我将执行getAccessToken()函数来发送过期的令牌,然后将刷新后的令牌发送给我。 最大的问题是,如果我的页面发出多个ajax请求,则如果第一个请求使我的令牌无效,第二个请求将强制我重新登录,因为它发送了空令牌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | @NgModule({ providers: [ { provide: AuthHttp, useFactory: authHttpServiceFactory, deps: [Http, RequestOptions, Router] } ] }) export function authHttpServiceFactory(http: Http, options: RequestOptions, router: Router) { return new AuthHttp(new AuthConfig({ tokenName: 'token', tokenGetter: (() => getAccessToken(http,router)), //tokenGetter: (() => localStorage.getItem('JWToken')), globalHeaders: [{'Content-Type': 'application/json'}], noJwtError: true, }), http, options); } function getAccessToken(http: Http, router:Router): Promise<string> { let jwtHelper: JwtHelper = new JwtHelper(); let accessToken = localStorage.getItem('JWToken'); if( accessToken == '' || !accessToken || accessToken == undefined || accessToken == null){ router.navigate(['./admin/login']); return; } if (jwtHelper.isTokenExpired(accessToken)) { return new Promise((resolve, reject) => { let refreshTokenService: RefreshTokenService = new RefreshTokenService(http); refreshTokenService.refreshToken(accessToken).subscribe((res: any) => { res = res.json(); if(res.token) { localStorage.setItem('JWToken', res.token); resolve(res.token); }else{ localStorage.removeItem('JWToken'); router.navigate(['./admin/login']); } }); }); } else { return Promise.resolve(accessToken); } } |
我希望请求等待第一个请求的响应
- 使用特殊服务发送您应用中的所有HTTP请求
- 将401响应与可观察到的返回值存储在该服务内部的缓冲区中,您将返回给调用方。 第一401发送令牌刷新请求
- 获得新令牌后,请使用新令牌重复缓冲区中的所有请求,并使用新响应调用其可观察对象。
这是新的httpClient库的注入器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | import {Injectable, Injector} from"@angular/core"; import {HttpEvent, HttpHandler, HttpInterceptor, HttpResponse} from"@angular/common/http"; import {HttpRequest} from"@angular/common/http"; import {Observable} from"rxjs/Observable"; import {SiteService} from"../services/site.service"; import {Router} from"@angular/router"; import {LoadingService} from"../../components/loading/loading.service"; import {AuthenticationService} from"../services/authentication.service"; @Injectable() export class AuthInterceptor implements HttpInterceptor { constructor(private router: Router, private siteService: SiteService, private loadingService: LoadingService, private injector: Injector) { } private fixUrl(url: string) { if (url.indexOf('http://') >= 0 || url.indexOf('https://') >= 0) return url; else return this.siteService.apiDomain() + url; } intercept(req: HttpRequest, next: HttpHandler): Observable<HttpEvent> { let clonedRequest; if ( this.siteService.getJWToken() !== null ) { clonedRequest = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + this.siteService.getJWToken()), url: this.fixUrl(req.url) }); } else { clonedRequest = req.clone({ url: this.fixUrl(req.url) }); } let authenticationService = this.injector.get(AuthenticationService); this.loadingService.start(); const started = Date.now(); return next.handle(clonedRequest) .do(event => { if (event instanceof HttpResponse) { const elapsed = Date.now() - started; console.log('%c Request for ' + this.fixUrl(req.urlWithParams) + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow'); } }) ._finally(() => { this.loadingService.stop(); }) .catch((res) => { if ((res.status === 401 || res.status === 403) && res.error.error === 'token_expired') { this.loadingService.start(); return authenticationService.refreshToken().flatMap((data: any) => { this.loadingService.stop(); if (data.token !== '') { this.siteService.setCurrentUser(data.user); this.siteService.setCurrentUserPermissions(data.permissions); this.siteService.setJWToken(data.token); } else { this.siteService.removeCurrentUser(); this.siteService.removeCurrentUserPermissions(); this.siteService.removeJWToken(); this.router.navigate(['./auth/login']); return Observable.throw(res); } let clonedRequestRepeat = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + this.siteService.getJWToken()), url: this.fixUrl(req.url) }); return next.handle(clonedRequestRepeat).do(event => { if (event instanceof HttpResponse) { const elapsed = Date.now() - started; console.log('%c Request for ' + req.urlWithParams + ' took ' + elapsed + ' ms.', 'background: #222; color: yellow'); } }); }) } else if (res.status === 400 && res.error.error === 'token_not_provided') { this.router.navigate(['./auth/login']); return Observable.throw(res); } else if (res.status === 401 && res.error.error === 'token_invalid') { this.router.navigate(['./auth/login']); return Observable.throw(res); } else { return Observable.throw(res); } }); } } |
并且不要忘记将缓存(浏览器)标头发送到后端响应至少几秒钟。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | function getAccessToken(http: Http, router: Router, refreshTokenService: RefreshTokenService): Promise<string> { let jwtHelper: JwtHelper = new JwtHelper(); let accessToken = localStorage.getItem('JWToken'); if (accessToken == '' || !accessToken || accessToken == undefined || accessToken == null) { router.navigate(['./admin/login']); return; } if (jwtHelper.isTokenExpired(accessToken)) { let waitPeriod = (!refreshTokenService.wait); refreshTokenService.wait = true; return new Promise((resolve, reject) => { if (waitPeriod) { refreshTokenService.refreshToken(accessToken).subscribe((res: any) => { res = res.json(); if (res.token) { localStorage.setItem('JWToken', res.token); resolve(res.token); refreshTokenService.wait = false; } else { localStorage.removeItem('JWToken'); router.navigate(['./admin/login']); } }); } else { let interval = setInterval(function () { if(refreshTokenService.wait == false) { resolve(localStorage.getItem('JWToken')); clearInterval(interval); } }, 500); } }); } else { return Promise.resolve(accessToken); } } |