【问题标题】:Angular. What is the correct way of refresh token implementation?角。刷新令牌实现的正确方法是什么?
【发布时间】:2018-04-30 10:53:49
【问题描述】:

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

我想做下一件事:

当有任何 已签名(已签名 - 表示为经过身份验证的用户提供 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 是异步的,我无法返回(实际上调用)原始方法。我应该如何处理?

我的代码示例如下:

数据服务

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

【问题讨论】:

  • 顺便说一句 - 我认为在本地存储中保存刷新令牌不是一个好主意...

标签: angular typescript angular5


【解决方案1】:

我认为您可以考虑使用依赖注入而不是服务继承。将诸如“refreshToken”之类的方法移动到 AuthService 是个好主意,但您也应该将“handleError”方法移动到 AuthService,因为该方法也应该只处理身份验证错误,例如更新令牌或注销用户。所以在那之后你不再需要继承你的 ApiService 了,我完全不明白它的目的。

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

已经在 github 上提供了一个不错的示例 hereLink 完整指南。

【讨论】:

  • 我考虑使用 HttpInterceptors,我想这就是我想要的。
猜你喜欢
  • 2020-11-20
  • 2019-02-16
  • 1970-01-01
  • 2011-11-06
  • 2018-08-12
  • 1970-01-01
  • 1970-01-01
  • 2020-01-17
  • 2016-12-23
相关资源
最近更新 更多