Ионная память get возвращает обещание "Bearer [object Promise]" Как вернуть значение и использовать его в качестве токена авторизации? - PullRequest
1 голос
/ 28 мая 2019

Ионная storage.get('token').then() функция возвращает обещание, поэтому она возвращает объект обещания вместо токена обновления.

Я работаю над угловым проектом Ionic 4, где я использую JWT для аутентификации. Используя перехватчик HTTP, я смог отправить токен доступа в качестве токена носителя заголовков авторизации. Поскольку срок действия JWT истекает очень быстро, мне нужно обновить токен. Я использую бэкэнд Python и Flask, где после успешного входа в систему ответ сервера содержит и доступ, т.е. JWT и токен обновления. На моем сервере Python для обновления токена мне нужно сделать POST-запрос к конечной точке обновления с токеном обновления в качестве маркера-носителя для заголовков авторизации. В ответ сервер отправляет мне токен доступа.

Шаги, которые я выполнил:

  1. После успешного входа я сохраняю токен доступа и токен обновления в хранилище Ionic.
  2. Отправка токена доступа с каждым запрос с использованием углового HTTP-перехватчика.
  3. Если есть ошибка, ответ сервера с соответствующим кодом ответа об ошибке, то я отправка запроса на обновление токена, добавление токена обновления в качестве заголовок авторизации токена на предъявителя
  4. Тогда с ответа сервера снова сохранить токен доступа в хранилище Ionic и добавить новый токен доступа с каждым запросом.

Проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю запрос на обновление токена вместо того, чтобы отправлять токен обновления в качестве заголовка авторизации, запрос отправляет «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.log(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.log(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.log(this.storage.get(this.ACCESS_TOKEN));
          // console.log(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.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(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.log('executed');
          console.log(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.log(token);
          console.log('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));
        }));
    }
  }


}


Ответы [ 2 ]

1 голос
/ 12 июня 2019

Я наконец-то смог это решить. Проблема была в моем перехватчике. В моем предыдущем коде я перехватывал каждый запрос и отправлял с ним токен носителя заголовка авторизации. Проблема с этим подходом заключалась в том, что когда я пытался получить токен доступа с помощью токена обновления, мой 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.log(req);
  return next.handle(clonedReq).pipe(catchError(error => {
    if (error instanceof HttpErrorResponse && error.status === 500) {
      // console.log('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))
    );
  }
0 голосов
/ 28 мая 2019

Вы можете переключить этот метод на async метод:

async getAccessTokenUsingRefreshToken() {
    const refreshToken = await this.storage.get('refresh_token');

    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.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(this.storage.get('access_token'));
      return tokens; // if not working try this "return of(tokens);"
    }));

  }

или выполнить всю логику для обновления в then части:

getAccessTokenUsingRefreshToken() {
    return this.storage.get('refresh_token').then((result) => {
      const httpOptions = {
      headers: new HttpHeaders({
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${result}`
      })
    };
    return this.http.post<any>(`${this.url}/token/refresh`, 'body', httpOptions ).pipe(tap(tokens => {
      console.log(tokens['access_token']);
      console.log(tokens);
      this.storage.set(this.ACCESS_TOKEN, tokens[this.ACCESS_TOKEN]);
      console.log(this.storage.get('access_token'));
      return tokens; // if not working try this "return of(tokens);"
    }));
    });
}

РЕДАКТИРОВАТЬ: Какой бы подход вы ни использовали (первый или второй выше), ваш метод вернет Promise<Observable<any>>.Итак, я рекомендую вам изменить метод handle401Error на что-то вроде этого:

private handle401Error(request: HttpRequest<any>, next: HttpHandler) {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      this.authenticationService.getAccessTokenUsingRefreshToken().then(obs => {
          return obs.pipe(
            switchMap((token: any) => {
              this.isRefreshing = false;
              console.log(token);
              console.log('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));
        }));
    }
  }

Это должно исправить ошибку, о которой вы упоминали в комментарии ... Кроме того, я предлагаю вам использовать возвращаемые типы в ваших методах, чтобы лучшепонять, что происходит (и что кто-то еще сможет лучше понять ваш код в будущем) ...

Наконец, если это все еще не работает, я рекомендую сделать пример stackblitz такмы можем воспроизвести вашу проблему (и проверить ее, прежде чем ответить вам).Я попытался создать пример, но я застрял со всеми этими импортами для ionic и angular-jwt, поэтому я не могу проверить свои решения, и мои ответы могут быть неправильными: (

Тем не менее, надеюсь, это поможет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...