离子存储获取返回一个承诺“Bearer [object Promise]”如何返回一个值并将其用作授权令牌?

Posted

技术标签:

【中文标题】离子存储获取返回一个承诺“Bearer [object Promise]”如何返回一个值并将其用作授权令牌?【英文标题】:Ionic storage get returns a promise "Bearer [object Promise]" How to return a value and use it as authorization token? 【发布时间】:2019-10-13 15:49:37 【问题描述】:

Ionic storage.get('token').then() 函数返回一个承诺,因此它返回一个承诺对象而不是刷新令牌。

我正在使用 JWT 进行身份验证的 Ionic 4 Angular 项目。使用 HTTP 拦截器,我能够将访问令牌作为授权标头不记名令牌发送。因为 JWT 很快就会过期,所以我需要刷新令牌。我正在使用 Python 和 Flask 后端,成功登录后,服务器响应包含访问权限,即 JWT 和刷新令牌。在我的 Python 服务器中刷新令牌,我需要使用刷新令牌作为授权标头不记名令牌向刷新端点发出 POST 请求。作为响应,服务器将访问令牌发送给我。

我遵循的步骤是:

    成功登录后,我将访问令牌和刷新令牌保存在 Ionic 存储中。 发送访问令牌与每个 使用 Angular HTTP 拦截器请求。 如果出现错误 服务器响应带有适当的错误响应代码然后我是 发送刷新令牌请求,将刷新令牌添加为 不记名令牌授权标头 然后从服务器响应 再次将访问令牌保存在 Ionic 存储中并添加新的 每个请求的访问令牌。

我面临的问题是,当我发送刷新令牌请求而不是发送刷新令牌作为授权标头时,请求正在发送“Bearer [object Promise]”。

问题出在我的身份验证服务和getAccessTokenUsingRefreshToken( ) 函数中,该函数返回一个可观察值。 因为this.storage.get(‘refresh_token’).then( ) 返回一个promise,所以它返回一个promise 对象而不是token。

我的鉴权服务代码如下:

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));
        ));
    
  





【问题讨论】:

.then(result =&gt; return result; ) 完全没有意义;它已经是该结果值的承诺。你已经从那个方法返回了一个 observable,为什么不再次使用from 是的,我只是想看看结果。我将编辑那部分代码 【参考方案1】:

我终于能够解决它。问题出在我的拦截器上。在我之前的代码中,我拦截了每一个请求,并用它发送了 Authorization 标头不记名令牌。这种方法的问题在于,当我尝试使用刷新令牌获取访问令牌时,我的 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));
    ));




这是我在 AuthenticationService 中的最终 getAccessTokenUsingRefreshToken() 方法:

    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))
    );
  

【讨论】:

以上是关于离子存储获取返回一个承诺“Bearer [object Promise]”如何返回一个值并将其用作授权令牌?的主要内容,如果未能解决你的问题,请参考以下文章

离子存储在承诺中未定义?

未定义对象数组的离子存储

如何在线性流之后链接承诺?

错误:未捕获(承诺):覆盖不存在(离子可选)

离子 - 错误:未捕获(承诺):找不到removeView

如何使用离子5中的离子相机插件将图像存储在数据库中