Angular - JWT 刷新令牌
Angular - JWT Refresh Token
找了半天也没找到refresing tokens的方法
TTL:30分钟
刷新 TTL:2 周
如果我在 45 分钟后刷新页面,然后我创建一个 getAccessToken() 函数来发送过期的令牌,然后给我发回一个刷新的令牌。最大的问题是,如果我的页面发出超过 1 个 ajax 请求,那么如果第一个请求使我的令牌无效,第二个请求会强制我重新登录,因为它发送空令牌
@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 响应与您将 return 给调用者的可观察值一起存储在此服务的缓冲区中。第一个401发送token刷新请求
- 获得新令牌后,使用新令牌重复缓冲区中的所有请求,并使用新响应调用它们的可观察对象。
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);
}
}
这是新的 httpClient 库的注入器
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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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);
}
});
}
}
并且不要忘记将缓存(浏览器)headers 发送到后端响应至少几秒钟。
找了半天也没找到refresing tokens的方法 TTL:30分钟 刷新 TTL:2 周
如果我在 45 分钟后刷新页面,然后我创建一个 getAccessToken() 函数来发送过期的令牌,然后给我发回一个刷新的令牌。最大的问题是,如果我的页面发出超过 1 个 ajax 请求,那么如果第一个请求使我的令牌无效,第二个请求会强制我重新登录,因为它发送空令牌
@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 响应与您将 return 给调用者的可观察值一起存储在此服务的缓冲区中。第一个401发送token刷新请求
- 获得新令牌后,使用新令牌重复缓冲区中的所有请求,并使用新响应调用它们的可观察对象。
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);
}
}
这是新的 httpClient 库的注入器
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<any>, next: HttpHandler): Observable<HttpEvent<any>> {
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);
}
});
}
}
并且不要忘记将缓存(浏览器)headers 发送到后端响应至少几秒钟。