【问题标题】:Acquiring a new token with `axios.interceptors`使用 `axios.interceptors` 获取新令牌
【发布时间】:2019-12-06 16:02:55
【问题描述】:

token过期后,我想根据refresh_token获取一个新的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'));

【问题讨论】:

  • 我认为这个问题太宽泛了,你到底想要做什么。什么有效,什么无效?你的回答问题太多了,最好整理一下,以免在阅读时松散人。

标签: javascript reactjs oauth axios


【解决方案1】:

这是我以前做过的。你的配置和我的有点不同。

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 小时,因为在 24 小时之前永远不会调用 refreshToken() 方法。 你不需要在这里处理client_idsecret_idgrant_type

【讨论】:

  • 什么意思?为什么我需要创建一个新实例。请给我解释一下? const axiosInstance = axios.create(defaultOptions); 那么一些failedQueue 呢?我需要创建一个failedQueue 数组吗?在此代码中再次设置 localstorage 中的令牌在哪里?毕竟需要在localStorage中设置token?
  • 我将在 Google Drive 中附加一个新文件,然后检查该文件。这是链接:docs.google.com/document/d/…
【解决方案2】:

请检查我是否正确配置了 axios.interceptors。

我认为它有效。但我建议你还是要仔细测试一下。这篇文章不错,可以参考https://blog.liplex.de/axios-interceptor-to-refresh-jwt-token-after-expiration/

我是否将它放在正确的位置,即在 Items 类上方。 ?

你应该创建一个服务函数来包装 Axios 和 API 配置,当然还有拦截器

axios.interceptors.response 分配给拦截器变量。我应该如何处理这个变量?

它只是一个用于定义拦截器的变量。别管它。如果您想避免分配它,只需在类似 Automating access token refreshing via interceptors in axios 的函数中执行它

我必须等待 24 小时才能测试它是否有效,或者是否有可能以不同的方式更快?

您可以更改保存在 localStorage 中的令牌,然后这样做

我应该把'client_id'、'secret_id'、'grant_type'放在哪里?

如果您将其存储在 localStorage 中,则页面内的任何脚本都可以访问它(这听起来就像 XSS 攻击可以让外部攻击者访问令牌一样糟糕)。

不要将其存储在本地存储(或会话存储)中。如果您在页面中包含的第 3 部分脚本中的任何一个被泄露,它可以访问您所有用户的令牌。

JWT 需要存储在 HttpOnly cookie 中,这是一种特殊类型的 cookie,仅在 HTTP 请求中发送到服务器,并且永远无法从浏览器中运行的 JavaScript 访问(读取或写入)。

【讨论】:

    【解决方案3】:

    请检查我是否正确配置了 axios.interceptors。

    据我所见,配置似乎还可以,因为它与https://stackoverflow.com/a/53294310/4229159的答案相同

    我是否将它放置在正确的位置,即在 Items 类之上。 ?

    这是我无法回答的问题,每个应用程序都不同,这不是放置它的最佳位置,但作为示例可能还可以。 在您的应用中,它应该与所有 API 调用(例如)一起使用

    axios.interceptors.response 被赋值给interceptor 变量。我应该如何处理这个变量?

    如您所见,如果您的后端从那里读取 auth 值,则从调用 /refresh_token 得到回答的变量分配给 config.headers['Authorization'] = 'Bearer ' + response.data.access_token; 应该没问题

    我必须等待 24 小时才能测试它是否有效,或者是否有可能以不同的方式更快?

    您应该等待,除非后端可以更改它,并在更短的时间内使令牌过期(EG 在 5 或 2 分钟内)

    我应该把'client_id'、'secret_id'、'grant_type'放在哪里?

    似乎后端应该有这个,除非它们是公共的......你可能最好知道它是否属于调用的配置,或者你是否正在与他们进行身份验证。 如果您正在与他们进行身份验证并且他们是授予您令牌的人,那么您不应该将其放在客户端,因为这存在安全风险

    【讨论】:

      【解决方案4】:

      1) 配置对我来说看起来不错。但是当有多个并行请求并且所有请求同时尝试刷新身份验证令牌时,您的解决方案将不起作用。相信我,这是一个很难确定的问题。所以最好提前覆盖。

      2) 不,不是正确的地方。创建一个单独的服务(我称之为 api.service)并使用它进行所有网络/api 交换。

      3) 没有使用拦截器变量。您可以避免将其分配给变量。

      4) 如果可以控制 API,您可以稍微减少超时。我也觉得24小时有点太长了。否则我猜没有选择。

      5) 不确定你必须处理它们。

      Bellow 是 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);
      });
      

      干杯,

      【讨论】:

        猜你喜欢
        • 2020-09-25
        • 2015-07-19
        • 2019-05-15
        • 2015-04-10
        • 2019-02-25
        • 2019-10-26
        • 2016-09-28
        • 2015-11-26
        • 1970-01-01
        相关资源
        最近更新 更多