Получение нового токена с помощью `axios.interceptors`

Когда срок действия токена истекает, я хочу получить новый токен на основе refresh_token. Я читал, что это можно получить с помощью axios.interceptors.

Пожалуйста, проверьте, если:

  • Правильно ли я настроил axios.interceptors?
  • Правильно ли я разместил его, то есть над классомItems.
  • axios.interceptors.response присваивается переменной interceptor. Что мне делать с этой переменной?

В дополнение к `axios.interceptors' мне нужно получить новый токен. Токен действителен в течение 24 часов.

  • Нужно ли ждать 24 часа, чтобы проверить, работает ли это, или можно по-другому, быстрее?
  • Где я должен указать «client_id», «secret_id», «grant_type»?

Код здесь: https://stackblitz.com/edit/react-pkea41

import axios from 'axios';

axios.defaults.baseURL = localStorage.getItem('domain');

const interceptor = axios.interceptors.response.use(
  response => response,
  error => {
      // Reject promise if usual error
      if (errorResponse.status !== 401) {
          return Promise.reject(error);
      }

      /* 
       * When response code is 401, try to refresh the token.
       * Eject the interceptor so it doesn't loop in case
       * token refresh causes the 401 response
       */
      axios.interceptors.response.eject(interceptor);

      return axios.post('/api/refresh_token', {
          'refresh_token': JSON.parse(localStorage.getItem('token'))['refresh_token']
      }).then(response => {
          /*saveToken();*/
          localStorage.setItem('token', JSON.stringify(response.data));
          error.response.config.headers['Authorization'] = 'Bearer ' + response.data.access_token;
          return axios(error.response.config);
      }).catch(error => {
          /*destroyToken();*/
          localStorage.setItem('token', '');
          this.router.push('/login');
          return Promise.reject(error);
      }).finally(createAxiosResponseInterceptor);
  }
);

class Items extends Component {

  constructor (props) {
    super(props);
    this.state = {

    }
  }


  render () {
    return (
      <div >

      </div>
    )
  }
}


render(<Items />, document.getElementById('root'));

Я думаю, что вопрос слишком широк, что такое именно то, что вы пытаетесь сделать. Что работает, а что нет? В вашем ответе слишком много вопросов и было бы лучше систематизировать их, чтобы не терять людей во время чтения.

Alejandro Vales 02.08.2019 11:28
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
6
1
14 503
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Please check if I have correctly configured axios.interceptors.

Из того, что я вижу, конфигурация выглядит нормально, так как она такая же, как и в этом ответе https://stackoverflow.com/a/53294310/4229159.

Have I placed it in the right place, i.e. above theItems class. ?

Это то, на что я не могу ответить, каждое приложение отличается, это не лучшее место для этого, но для примера может подойти. Однако в вашем приложении он должен быть вместе со всеми вызовами API (например)

axios.interceptors.response is assigned to theinterceptor variable. What should I do with this variable?

Как видите, переменная, которая получила ответ от вызова /refresh_token, назначена config.headers['Authorization'] = 'Bearer ' + response.data.access_token;, если ваш бэкенд читает оттуда значение аутентификации, все должно быть в порядке.

I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?

Вам следует подождать, если бэкэнд не сможет это изменить, и истечет срок действия токена за меньшее время (например, через 5 или 2 минуты).

Where should I put 'client_id', 'secret_id', 'grant_type'?

Похоже, что бэкэнд должен иметь это, если только они не являются общедоступными ... Вероятно, вам лучше всего знать, относится ли это к конфигурации для вызова или вы проходите аутентификацию с ними. Если вы проходите аутентификацию с ними, и именно они предоставляют вам токен, то вам не следует размещать его на стороне клиента, так как это представляет угрозу безопасности.

Это то, что я сделал раньше. Ваша конфигурация немного отличается от моей.

const baseURL = localStorage.getItem('domain');
const defaultOptions = {
  baseURL,
  method: 'get',
  headers: {
    'Content-Type': 'application/json',
  }
};
// Create Instance
const axiosInstance = axios.create(defaultOptions);
// Get token from session
const accessToken = ...

// Set the auth token for any request
instance.interceptors.request.use(config => {
  config.headers.Authorization = accessToken ? `Bearer ${accessToken}` : '';
  return config;
});

// Last step: handle request error general case
instance.interceptors.response.use(
  response => response,
  error => {
    // Error
    const { config, response: { status } } = error;

    if (status === 401) {
      // Unauthorized request: maybe access token has expired!
      return refreshAccessToken(config);
    } else {
      return Promise.reject(error);
    }
  }
});

Я думаю, что эту часть следует отделить от Компонентов - она ​​будет размещена на хелперах или утилитах. Кроме того, вам придется ждать 24 часа, потому что метод refreshToken() никогда не вызывается раньше 24 часов. Вам не нужно обрабатывать client_id, secret_id, grant_type прямо здесь.

Что это означает? Зачем мне создавать новый экземпляр. Пожалуйста, объясните мне это? const axiosInstance = axios.create(defaultOptions); А как насчет некоторых failedQueue? Нужно ли мне создавать массив failedQueue? И где в этом коде снова устанавливается токен в localstorage? Ведь мне нужно установить токен в localStorage?

Umbro 03.08.2019 15:25

Я прикреплю новый файл на Google Диске, а затем проверю этот файл. Вот ссылка: docs.google.com/document/d/…

user11869519 08.08.2019 05:42
Ответ принят как подходящий

Please check if I have correctly configured axios.interceptors.

Я думаю, это работает. Но я предлагаю вам тщательно протестировать его. Это хорошая статья для ссылки https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/

Have I placed it in the right place, i.e. above theItems class. ?

Вы должны создать сервисную функцию для переноса конфигураций Axios и API и, конечно же, перехватчика.

axios.interceptors.response is assigned to the interceptor variable. What should I do with this variable?

Это просто переменная, используемая для определения перехватчика. Плевать на это. Если вы хотите избежать его назначения, просто сделайте это внутри такой функции, как эта Автоматическое обновление токена доступа с помощью перехватчиков в axios

I have to wait 24 hours to test whether it works, or is it possible in a different way, faster?

Вы можете изменить токен, сохраненный в вашем локальном хранилище, и сделать это

Where should I put 'client_id', 'secret_id', 'grant_type'?

Если вы храните его внутри localStorage, он доступен любому сценарию на вашей странице (что так же плохо, как и звучит, поскольку атака XSS может позволить внешнему злоумышленнику получить доступ к токену).

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

JWT должен храниться внутри файла cookie HttpOnly, особого типа файла cookie, который отправляется только в HTTP-запросах на сервер и никогда не доступен (как для чтения, так и для записи) из JavaScript, запущенного в браузере.

1) Конфигурация выглядит нормально для меня. Но ваше решение не будет работать, когда есть несколько параллельных запросов, и все они пытаются обновить токен аутентификации одновременно. Поверьте мне, это проблема, которую действительно трудно точно определить. Так что лучше прикрыться заранее.

2) Нет. Не то место. Создайте отдельную службу (я называю ее api.service) и выполняйте всю коммутацию сети/API, используя ее.

3) Переменная-перехватчик не используется. Вы можете не присваивать его переменной.

4) Если у вас есть контроль над API, вы можете немного уменьшить время ожидания. Также я думаю, что 24 часа слишком долго. Иначе без вариантов, я думаю.

5) Не уверен, что вам придется иметь с ними дело.

Ниже приведен рабочий код api.service.ts. Возможно, вам придется изменить несколько вещей здесь и там, чтобы соответствовать вашему приложению. Если вы четко усвоите концепцию, это не составит труда. Также он охватывает проблему с несколькими параллельными запросами.

import * as queryString from 'query-string';

import axios, { AxiosRequestConfig, Method } from 'axios';

import { accountService } from '../account.service'; //I use account service to authentication related services
import { storageService } from './storage.service'; //I use storage service to keep the auth token. inside it it uses local storage to save values

var instance = axios.create({
  baseURL: 'your api base url goes here',
});
axios.defaults.headers.common['Content-Type'] = 'application/json';

export const apiService = {
  get,
  post,
  put,
  patch,
  delete: deleteRecord,
  delete2: deleteRecord2
}

function get<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<T>('get', controller, action, null, urlParams, queryParams);
}

function post<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<T>('post', controller, action, data, urlParams, queryParams);
}

function put<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<T>('put', controller, action, data, urlParams, queryParams);
}

function patch<T>(controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<T>('patch', controller, action, data, urlParams, queryParams);
}
function deleteRecord(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<any>('delete', controller, action, null, urlParams, queryParams);
}

function deleteRecord2<T>(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
  return apiRequest<T>('delete', controller, action, null, urlParams, queryParams);
}

function apiRequest<T>(method: Method, controller: string, action: string = '', data: any, urlParams: string[] = [], queryParams: any = null) {
  var url = createUrl(controller, action, urlParams, queryParams);
  var options = createRequestOptions(url, method, data);

  return instance.request<T>(options)
    .then(res => res && res.data)
    .catch(error => {
      if (error.response) {
        //handle error appropriately: if you want to display a descriptive error notification this is the place
      } else {
        //handle error appropriately: if you want to display a a generic error message
      }
      throw error;
    });
}

function createUrl(controller: string, action: string = '', urlParams: string[] = [], queryParams: any = null) {
  let url = controller + (action ? '/' + action : '');

  urlParams.forEach(param => {
    url += '/' + param;
  });

  let params = '';
  if (queryParams) {
    params += '?' + queryString.stringify(queryParams);
  }

  return url += params;
}

function createRequestOptions(url: string, method: Method, data: any, responseType?: any) {
  var authToken = storageService.getAuthToken();
  var jwtToken = authToken != null ? authToken.authToken : '';

  var options: AxiosRequestConfig = {
    url,
    method,
    data,
    headers: {
      'Authorization': 'bearer ' + jwtToken
    },
  }

  if (responseType) {
    options.responseType = responseType;
  }

  return options;
}

let isRefreshing = false;
let failedQueue: any[] = [];

const processQueue = (error: any, token: string = '') => {
  failedQueue.forEach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  });

  failedQueue = [];
}

instance.interceptors.response.use(undefined, (error) => {
  const originalRequest = error.config;
  if (originalRequest && error.response && error.response.status === 401 && !originalRequest._retry) {
    if (isRefreshing) {
      return new Promise(function (resolve, reject) {
        failedQueue.push({ resolve, reject })
      }).then(authToken => {
        originalRequest.headers.Authorization = 'bearer ' + authToken;
        return axios(originalRequest);
      }).catch(err => {
        return err;
      })
    }

    originalRequest._retry = true;
    isRefreshing = true;

    return new Promise(function (resolve, reject) {
      accountService.refreshToken()
        .then(result => {
          if (result.succeeded) {
            originalRequest.headers.Authorization = 'bearer ' + result.authToken;
            axios(originalRequest).then(resolve, reject);
            processQueue(null, result.authToken);
          } else {
            reject(error);
          }
        }).catch((err) => {
          processQueue(err);
          reject(err);
        }).then(() => { isRefreshing = false });
    });
  }
  return Promise.reject(error);
});

Ваше здоровье,

Другие вопросы по теме