Функция Ionic storage.get('token').then()
возвращает обещание, поэтому она возвращает объект обещания вместо токена обновления.
Я работаю над угловым проектом Ionic 4, где я использую JWT для аутентификации. Используя перехватчик HTTP, я смог отправить токен доступа в качестве токена-носителя заголовков авторизации. Поскольку срок действия JWT истекает очень быстро, мне нужно обновить токен. Я использую бэкэнд Python и Flask, где после успешного входа в систему ответ сервера содержит как доступ, то есть JWT, так и токен обновления. На моем сервере Python для обновления токена мне нужно выполнить запрос POST к конечной точке обновления с токеном обновления в качестве токена-носителя заголовков авторизации. В ответ сервер отправляет мне токен доступа.
Шаги, которые я выполнил:
Проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю запрос токена обновления вместо отправки токена обновления в качестве заголовка авторизации, запрос отправляет «Bearer [object Promise]».
Проблема в моей службе аутентификации и функции getAccessTokenUsingRefreshToken( )
, которая возвращает наблюдаемое.
поскольку this.storage.get(‘refresh_token’).then( )
возвращает обещание, он возвращает объект обещания вместо токена.
Код моей службы аутентификации выглядит следующим образом:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpErrorResponse, HttpRequest } from '@angular/common/http';
import { BehaviorSubject, throwError, Observable, from } from 'rxjs';
import { Platform, AlertController } from '@ionic/angular';
import { Storage } from '@ionic/storage';
import { JwtHelperService } from '@auth0/angular-jwt';
import { tap, catchError, mergeMap } from 'rxjs/operators';
import { User } from '../models/user.model';
@Injectable({
providedIn: 'root'
})
export class AuthenticationService {
constructor(
private http: HttpClient,
private helper: JwtHelperService,
private storage: Storage,
private platform: Platform,
private alertController: AlertController) {
// this.platform.ready().then(() => {
// this.checkToken();
// });
}
url = 'http://localhost:5000';
ACCESS_TOKEN = 'access_token';
REFRESH_TOKEN = 'refresh_token';
user = null;
token;
// refreshToken;
authenticationState = new BehaviorSubject(false);
register(user: User): Observable<User> {
// if (user.id === null)
console.info(user);
return this.http.post<User>(`${this.url}/register`, user)
.pipe(
tap(res => {
this.storage.set(this.ACCESS_TOKEN, res[this.ACCESS_TOKEN]);
this.storage.set(this.REFRESH_TOKEN, res[this.REFRESH_TOKEN]);
this.user = this.helper.decodeToken(res[this.ACCESS_TOKEN]);
// console.info(this.storage.get(this.REFRESH_TOKEN));
this.authenticationState.next(true);
}),
);
}
login(data) {
return this.http.post(`${this.url}/auth`, data)
.pipe(
tap(res => {
this.storage.set(this.ACCESS_TOKEN, res[this.ACCESS_TOKEN]);
this.storage.set(this.REFRESH_TOKEN, res[this.REFRESH_TOKEN]);
this.user = this.helper.decodeToken(res[this.ACCESS_TOKEN]);
// this.storage.get(this.REFRESH_TOKEN);
// console.info(this.storage.get(this.ACCESS_TOKEN));
// console.info(this.getRefreshToken());
this.authenticationState.next(true);
}),
);
}
logout() {
this.storage.remove(this.ACCESS_TOKEN).then(() => {
this.authenticationState.next(false);
});
this.storage.remove(this.REFRESH_TOKEN);
}
private addToken(token: any) {
if (token) {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
})
};
return httpOptions;
}
}
getAccessTokenUsingRefreshToken() {
const refreshToken = this.storage.get('refresh_token').then((result) => {
return result;
});
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${refreshToken}`
})
};
return this.http.post<any>(`${this.url}/token/refresh`, 'body', httpOptions ).pipe(tap(tokens => {
console.info(tokens['access_token']);
console.info(tokens);
this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
console.info(this.storage.get('access_token'));
}));
}
checkToken(): Promise<any> {
return this.storage.get(this.ACCESS_TOKEN).then(token => {
if (token) {
this.token = token;
if (!this.helper.isTokenExpired(this.token)) {
this.user = this.helper.decodeToken(this.token);
this.authenticationState.next(true);
} else {
this.storage.remove(this.ACCESS_TOKEN);
this.storage.remove(this.REFRESH_TOKEN);
}
}
});
}
getToken() {
return this.storage.get(this.ACCESS_TOKEN);
}
isAuthenticated() {
return this.authenticationState.value;
}
}
Это мой код перехватчика HTTP
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
import { Observable, from, throwError, BehaviorSubject } from 'rxjs';
import { Storage } from '@ionic/storage';
// import { _throw } from 'rxjs/observable/throw';
import { catchError, mergeMap, switchMap, filter, take } from 'rxjs/operators';
import { AlertController } from '@ionic/angular';
import { AuthenticationService } from './authentication.service';
@Injectable({
providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private storage: Storage, private alertCtrl: AlertController, private authenticationService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
let promise = this.storage.get('access_token');
return from(promise).pipe(mergeMap(token => {
const clonedReq = this.addToken(req, token);
return next.handle(clonedReq).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 401) {
// console.info('executed');
console.info(req);
return this.handle401Error(req, next);
} else {
return throwError(error.message);
}
})
);
}
));
}
// Adds the token to your headers if it exists
private addToken(request: HttpRequest<any>, token: any) {
if (token) {
let clone: HttpRequest<any>;
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`,
Authorization: `Bearer ${token}`
}
});
return clone;
}
return request;
}
private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return this.authenticationService.getAccessTokenUsingRefreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
console.info(token);
console.info('executed');
this.refreshTokenSubject.next(token.access_token);
return next.handle(this.addToken(request, token.access_token));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(access_token => {
return next.handle(this.addToken(request, access_token));
}));
}
}
}
Да, я просто пытался увидеть результат. Я отредактирую эту часть кода
Наконец-то я смог ее решить. Проблема была в моем перехватчике. В моем предыдущем коде я перехватывал каждый запрос и отправлял с ним токен носителя заголовка авторизации. Проблема с этим подходом заключалась в том, что когда я пытался получить токен доступа с помощью токена обновления, мой HTTP-перехватчик также отправлял токен доступа с истекшим сроком действия в качестве заголовка авторизации. Поэтому у меня была логика в моем методе перехвата, где для любого запроса в конечной точке токена обновления должен быть только запрос без токена доступа.
if (req.url.endsWith('/token/refresh')) {
return next.handle(req);
}
Итак, это окончательный код InterceptorService.ts.
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpHeaders,
HttpErrorResponse } from '@angular/common/http';
import { Observable, from, throwError, BehaviorSubject } from 'rxjs';
import { Storage } from '@ionic/storage';
// import { _throw } from 'rxjs/observable/throw';
import { catchError, mergeMap, switchMap, filter, take, map } from 'rxjs/operators';
import { AlertController } from '@ionic/angular';
import { AuthenticationService } from './authentication.service';
@Injectable({
providedIn: 'root'
})
export class InterceptorService implements HttpInterceptor {
private isRefreshing = false;
private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null);
constructor(private storage: Storage, private alertCtrl: AlertController,
private authenticationService: AuthenticationService) { }
intercept(req: HttpRequest<any>, next: HttpHandler):
Observable<HttpEvent<any>> {
// sending the request only for the refresh token endpoint
if (req.url.endsWith('/token/refresh')) {
return next.handle(req);
}
let promise = this.storage.get('access_token');
return from(promise).pipe(mergeMap(token => {
const clonedReq = this.addToken(req, token);
console.info(req);
return next.handle(clonedReq).pipe(catchError(error => {
if (error instanceof HttpErrorResponse && error.status === 500) {
// console.info('executed');
return this.handleAccessError(req, next);
} else {
return throwError(error.message);
}
})
) as any;
}
)) as any;
}
// Adds the token to your headers if it exists
private addToken(request: HttpRequest<any>, token: any) {
if (token) {
let clone: HttpRequest<any>;
clone = request.clone({
setHeaders: {
Accept: `application/json`,
'Content-Type': `application/json`,
Authorization: `Bearer ${token}`
}
});
return clone;
}
return request;
}
private handleAccessError(request: HttpRequest<any>, next: HttpHandler) {
if (!this.isRefreshing) {
this.isRefreshing = true;
this.refreshTokenSubject.next(null);
return this.authenticationService.getAccessTokenUsingRefreshToken().pipe(
switchMap((token: any) => {
this.isRefreshing = false;
this.refreshTokenSubject.next(token);
return next.handle(this.addToken(request, token));
}));
} else {
return this.refreshTokenSubject.pipe(
filter(token => token != null),
take(1),
switchMap(jwt => {
return next.handle(this.addToken(request, jwt));
}));
}
}
}
Это мой последний метод getAccessTokenUsingRefreshToken() в AuthenticationService:
getAccessTokenUsingRefreshToken(): Observable<string> {
return from(this.storage.get('refresh_token')).pipe(
switchMap(refreshToken => {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': `Bearer ${refreshToken}`
})
};
return this.http.post<any>(`${this.url}/token/refresh`, {}, httpOptions);
}),
map(response => response.access_token),
tap(accessToken => this.storage.set(this.ACCESS_TOKEN, accessToken))
);
}
.then(result => { return result; })
совершенно бессмысленно; это уже было обещание того значения результата. Вы уже возвращаете наблюдаемое из этого метода, почему бы не использоватьfrom
снова?