角。刷新令牌实现的正确方法是什么?

时间:2018-04-30 10:53:49

标签: angular typescript angular5

目前我遇到了逻辑实现的问题。

我想做下一件事:

如果有任何签名(已签名 - 表示已提供JWT令牌,对于经过身份验证的用户)请求已向API后端发出,则API后端可能会返回401 JWT Token Expired。在这种情况下,我想再次调用刷新JWT令牌,然后(如果成功) - 再次发出原始请求。如果失败 - 重定向到登录页面。

此实施的当前问题是:

1)refreshToken()在ApiService中,但我认为它应该在AuthService中,因为它与身份验证有关。但是如果我移动方法 - 那么我必须在ApiService中注入AuthService(它扩展了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');
  }
}

1 个答案:

答案 0 :(得分:0)

我认为你可以考虑在服务继承上使用依赖注入。移动方法,如" refreshToken"到AuthService是个好主意,但你也应该移动" handleError" AuthService的方法,因为该方法也应该只处理身份验证错误,例如更新令牌或注销用户。所以在那之后你不再需要继承你的ApiService了,我根本不太了解它的用途。

之后,在您的DataService中,您只需注入您的AuthService(而不是继承),并通过向请求添加身份验证标头来修改发送请求。从我的角度来看,我认为最好将AuthService与DataService分开。

一个体面的例子已经在github上提供了hereLink完整指南。