Угловой. Как правильно использовать токен обновления? - PullRequest
0 голосов
/ 30 апреля 2018

В данный момент у меня проблема с реализацией логики.

Я хочу сделать следующее:

Когда есть какой-либо запрос подписанный (подписанный - означает с предоставленным токеном 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');
  }
}

1 Ответ

0 голосов
/ 30 апреля 2018

Я думаю, вы могли бы использовать инъекцию зависимостей в наследование сервисов. Перемещение таких методов, как «refreshToken», в AuthService - хорошая идея, но вам также следует переместить метод handleError в AuthService, поскольку этот метод также должен обрабатывать только ошибку аутентификации и, например, обновлять токен или выходить из системы. Поэтому после этого вам больше не нужно наследовать ваш ApiService, я не совсем понял его цель.

После этого в вашем DataService вы можете просто ввести свой AuthService (вместо наследования) и изменить запрос на отправку, добавив в запрос заголовки аутентификации. С моей точки зрения, я думаю, что лучше отделить AuthService от DataService.

Достойный пример уже предоставлен здесь на github. Ссылка на полное руководство.

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