В данный момент у меня проблема с реализацией логики.
Я хочу сделать следующее:
Когда есть какой-либо запрос подписанный (подписанный - означает с предоставленным токеном JWT, для аутентифицированных пользователей), отправленный в бэкэнд API, бэкэнд API может вернуть 401 JWT Token Expired. В этом случае я хочу сделать еще один вызов, чтобы обновить токен JWT, а затем (в случае успеха) сделать исходный запрос еще раз. Если не удалось - перенаправить на страницу входа.
Текущие проблемы с этой реализацией:
1) refreshToken () находится внутри ApiService, но я думаю, что он должен быть внутри AuthService, поскольку он связан с аутентификацией. Но если я переместил метод - тогда я должен внедрить AuthService (который расширяет ApiService) внутри ApiService, и тут возникает проблема тупиковой петли + я не знаю, как передать этот аргумент в конструкторе для AuthService, чтобы создать .super (args ) вызов.
2) Мой код на данный момент не работает, из-за этой части:
this.refreshToken().toPromise().then(() => {
if (request.data) {
console.log('POST');
return this[request.method](request.endpoint, request.data);
} else {
console.log('GET');
return this[request.method](request.endpoint);
}
});
, поскольку refreshToken является асинхронным, я не могу вернуть (фактически вызвать) исходный метод. Как мне с этим бороться?
Пример кода ниже:
DataService
import { Injectable } from '@angular/core';
import { Http, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Headers } from '@angular/http';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/throw';
import { NotFoundError } from '../errors/response-errors/not-found-error';
import { BadRequest } from '../errors/response-errors/bad-request';
import { AppError } from '../errors/app-error';
import { Unauthorized } from '../errors/response-errors/unauthorized';
@Injectable()
export class DataService {
protected lastRequest: any;
constructor(private url: string, private http: Http) {}
get(endpoint) {
console.log('called get: ' + endpoint);
console.log('THIS:', this);
this.lastRequest = {
'method': 'get',
'endpoint': endpoint,
};
return this.http
.get(this.url + endpoint, this.options)
.map((response) => {
const r = response.json();
console.log('Response (get):');
console.log(r);
return r;
// return response.json();
})
.catch(error => this.handleError(error));
}
post(endpoint, data) {
console.log('called post: ' + endpoint);
this.lastRequest = {
'method': 'post',
'endpoint': endpoint,
'data': data
};
return this.http
.post(this.url + endpoint, data, this.options)
.map((response) => {
const r = response.json();
console.log('Response:');
console.log(r);
return r;
// return response.json();
})
.catch(error => this.handleError(error));
}
get options() {
const token = localStorage.getItem('token');
if (token) {
const headers = new Headers();
headers.append('Authorization', 'Bearer ' + token);
return new RequestOptions({
headers: headers
});
}
return null;
}
protected handleError(error: Response): any {
if (error.status === 400) {
return Observable.throw(new BadRequest(error.json()));
}
if (error.status === 401) {
return Observable.throw(new Unauthorized());
}
if (error.status === 404) {
return Observable.throw(new NotFoundError());
}
return Observable.throw(new AppError(error.json()));
}
}
ApiService
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { DataService } from './data.service';
import { Observable } from 'rxjs/Observable';
import { Unauthorized } from '../errors/response-errors/unauthorized';
import { AuthService } from './auth.service';
@Injectable()
export class ApiService extends DataService {
constructor(http: Http) {
super('https://my-api-endpoint', http);
}
refreshToken() {
console.log('refreshToken clalled');
const refreshToken = localStorage.getItem('refresh_token');
console.log('using refresh token:', refreshToken);
if (refreshToken) {
return this.post('/renew-access-token', {
refresh_token: refreshToken
})
.map(response => {
console.log('got refreshToken response:');
console.log(response);
localStorage.setItem('token', response.token);
localStorage.setItem('refresh_token', response.refresh_token);
});
}
}
protected handleError(error: Response) {
if (error.status === 401) {
const res: any = error.json();
if (res && res.message === 'Expired JWT Token') {
const request = this.lastRequest;
console.log('last request:', request);
this.refreshToken().toPromise().then(() => {
if (request.data) {
console.log('POST');
return this[request.method](request.endpoint, request.data);
} else {
console.log('GET');
return this[request.method](request.endpoint);
}
});
}
console.log('NO');
return Observable.throw(new Unauthorized());
}
return super.handleError(error);
}
}
TendersService
import { Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { Http, URLSearchParams } from '@angular/http';
@Injectable()
export class TendersService extends ApiService {
constructor(http: Http) {
super(http);
}
getTenders(dateFrom, dateTo, status = 'actual', limit = 5, offset = 0) {
const params = new URLSearchParams();
params.set('date_from', dateFrom);
params.set('date_to', dateTo);
params.set('status', status);
params.set('limit', limit.toString());
params.set('offset', offset.toString());
return this.get('/tenders?' + params.toString());
}
}
AuthService
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { ApiService } from './api.service';
import 'rxjs/add/operator/map';
import { tokenNotExpired, JwtHelper } from 'angular2-jwt';
@Injectable()
export class AuthService extends ApiService {
constructor(http: Http) {
super(http);
}
login(credentials) {
return this.post('/login', credentials)
.map(response => {
localStorage.setItem('token', response.token);
localStorage.setItem('refresh_token', response.refresh_token);
});
}
logout() {
const refreshToken = localStorage.getItem('refresh_token');
if (refreshToken) {
this.post('/logout', {
refresh_token: refreshToken
}).toPromise();
}
localStorage.removeItem('token');
localStorage.removeItem('refresh_token');
}
isLoggedIn() {
return !!localStorage.getItem('refresh_token');
}
}