本文介绍了如何使用React Context正确设置Axios拦截器?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于我想使用React Context设置Axios拦截器,因此似乎可行的唯一解决方案是创建一个Interceptor组件,以便使用useContext钩子访问Context状态并进行调度.

Since I want to setup Axios interceptors with React Context, the only solution that seems viable is creating an Interceptor component in order to use the useContext hook to access Context state and dispatch.

问题是,这将创建一个闭包,并在调用时将旧数据返回到拦截器.

The problem is, this creates a closure and returns old data to the interceptor when it's being called.

我正在使用React/Node进行JWT身份验证,并且正在使用Context API存储访问令牌.

I am using JWT authentication using React/Node and I'm storing access tokens using Context API.

这是我的拦截器组件现在的样子:

This is how my Interceptor component looks like right now:

import React, { useEffect, useContext } from 'react';
import { Context } from '../../components/Store/Store';
import { useHistory } from 'react-router-dom';
import axios from 'axios';

const ax = axios.create();

const Interceptor = ({ children }) => {
  const [store, dispatch] = useContext(Context);

  const history = useHistory();

  const getRefreshToken = async () => {
    try {
      if (!store.user.token) {
        dispatch({
            type: 'setMain',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
          });

        const { data } = await axios.post('/api/auth/refresh_token', {
          headers: {
            credentials: 'include',
          },
        });

        if (data.user) {
          dispatch({
            type: 'setStore',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
            authenticated: true,
            token: data.accessToken,
            id: data.user.id,
            name: data.user.name,
            email: data.user.email,
            photo: data.user.photo,
            stripeId: data.user.stripeId,
            country: data.user.country,
            messages: {
              items: [],
              count: data.user.messages,
            },
            notifications:
              store.user.notifications.items.length !== data.user.notifications
                ? {
                    ...store.user.notifications,
                    items: [],
                    count: data.user.notifications,
                    hasMore: true,
                    cursor: 0,
                    ceiling: 10,
                  }
                : {
                    ...store.user.notifications,
                    count: data.user.notifications,
                  },
            saved: data.user.saved.reduce(function (object, item) {
              object[item] = true;
              return object;
            }, {}),
            cart: {
              items: data.user.cart.reduce(function (object, item) {
                object[item.artwork] = true;
                return object;
              }, {}),
              count: Object.keys(data.user.cart).length,
            },
          });
        } else {
          dispatch({
            type: 'setMain',
            loading: false,
            error: false,
            auth: store.main.auth,
            brand: store.main.brand,
            theme: store.main.theme,
          });
        }
      }
    } catch (err) {
      dispatch({
        type: 'setMain',
        loading: false,
        error: true,
        auth: store.main.auth,
        brand: store.main.brand,
        theme: store.main.theme,
      });
    }
  };

  const interceptTraffic = () => {
     ax.interceptors.request.use(
        (request) => {
            request.headers.Authorization = store.user.token
              ? `Bearer ${store.user.token}`
              : '';

            return request;
          },
        (error) => {
          return Promise.reject(error);
        }
      );

      ax.interceptors.response.use(
        (response) => {
          return response;
        },
        async (error) => {
          console.log(error);
          if (error.response.status !== 401) {
            return new Promise((resolve, reject) => {
              reject(error);
            });
          }

          if (
            error.config.url === '/api/auth/refresh_token' ||
            error.response.message === 'Forbidden'
          ) {
            const { data } = await ax.post('/api/auth/logout', {
              headers: {
                credentials: 'include',
              },
            });
            dispatch({
              type: 'resetUser',
            });
            history.push('/login');

            return new Promise((resolve, reject) => {
              reject(error);
            });
          }

          const { data } = await axios.post(`/api/auth/refresh_token`, {
            headers: {
              credentials: 'include',
            },
          });

          dispatch({
            type: 'updateUser',
            token: data.accessToken,
            email: data.user.email,
            photo: data.user.photo,
            stripeId: data.user.stripeId,
            country: data.user.country,
            messages: { items: [], count: data.user.messages },
            notifications:
              store.user.notifications.items.length !== data.user.notifications
                ? {
                    ...store.user.notifications,
                    items: [],
                    count: data.user.notifications,
                    hasMore: true,
                    cursor: 0,
                    ceiling: 10,
                  }
                : {
                    ...store.user.notifications,
                    count: data.user.notifications,
                  },
            saved: data.user.saved,
            cart: { items: {}, count: data.user.cart },
          });

          const config = error.config;
          config.headers['Authorization'] = `Bearer ${data.accessToken}`;

          return new Promise((resolve, reject) => {
            axios
              .request(config)
              .then((response) => {
                resolve(response);
              })
              .catch((error) => {
                reject(error);
              });
          });
        }
      );
  };

  useEffect(() => {
    getRefreshToken();
    if (!store.main.loading) interceptTraffic();
  }, []);

  return store.main.loading ? 'Loading...' : children;
}

export { ax };
export default Interceptor;

如果Cookie中存在刷新令牌,则每次用户刷新网站以检索访问令牌时,都会调用 getRefreshToken 函数.

The getRefreshToken function is called every time a user refreshes the website to retrieve an access token if there is a refresh token in the cookie.

interceptTraffic 函数是问题仍然存在的地方.它由一个请求拦截器和一个响应拦截器组成,该请求拦截器在每个请求后附加一个带有访问令牌的标头,该响应拦截器用于处理访问令牌的到期,以便使用刷新令牌来获取新令牌.

The interceptTraffic function is where the issue persists.It consists of a request interceptor which appends a header with the access token to every request and a response interceptor which is used to handle access token expiration in order to fetch a new one using a refresh token.

您会注意到我正在导出 ax (我在其中添加拦截器的 Axios 的实例),但是当在此组件之外调用它时,它引用的是旧的存储数据关闭.

You will notice that I am exporting ax (an instance of Axios where I added interceptors) but when it's being called outside this component, it references old store data due to closure.

这显然不是一个好的解决方案,但这就是为什么我需要帮助组织拦截器同时仍能够访问Context数据的原因.

This is obviously not a good solution, but that's why I need help organizing interceptors while still being able to access Context data.

请注意,我将此组件创建为包装器,因为它呈现了提供给它的子组件,这是App的主要组件.

Note that I created this component as a wrapper since it renders children that are provided to it, which is the main App component.

感谢您的帮助.

推荐答案

我有一个模板,该模板在每天有数百万访问量的系统中工作.

I have a template that works in a system with millions of access every day.

这解决了我的刷新令牌问题,并在不崩溃的情况下重新请求了

This solved my problems with refresh token and reattemp the request without crashing

首先,我有一个"api.js"与axios,配置,地址,标题.在此文件中,有两种方法,一种是使用auth,另一种是不使用.在同一文件中,我配置了拦截器:

First I have a "api.js" with axios, configurations, addresses, headers.In this file there are two methods, one with auth and another without.In this same file I configured my interceptor:

import axios from "axios";
import { ResetTokenAndReattemptRequest } from "domain/auth/AuthService";

export const api = axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    headers: {
        "Content-Type": "application/json",
    },
});

export const apiSecure = axios.create({
    baseURL: process.env.REACT_APP_API_URL,
    headers: {
        Authorization: "Bearer " + localStorage.getItem("Token"),
        "Content-Type": "application/json",
    },

    export default api;

    apiSecure.interceptors.response.use(
        function (response) {
            return response;
        },
        function (error) {
            const access_token = localStorage.getItem("Token");
            if (error.response.status === 401 && access_token) {
                return ResetTokenAndReattemptRequest(error);
            } else {
                console.error(error);
            }
            return Promise.reject(error);
        }
    );

然后使用ResetTokenAndReattemptRequest方法.我将其放置在另一个文件中,但是您可以将其放置在任意位置:

Then the ResetTokenAndReattemptRequest method. I placed it in another file, but you can place it wherever you want:

import api from "../api";
import axios from "axios";

let isAlreadyFetchingAccessToken = false;

let subscribers = [];

export async function ResetTokenAndReattemptRequest(error) {
  try {
    const { response: errorResponse } = error;
    const retryOriginalRequest = new Promise((resolve) => {
      addSubscriber((access_token) => {
        errorResponse.config.headers.Authorization = "Bearer " + access_token;
        resolve(axios(errorResponse.config));
      });
    });
    if (!isAlreadyFetchingAccessToken) {
      isAlreadyFetchingAccessToken = true;
      await api
        .post("/Auth/refresh", {
          Token: localStorage.getItem("RefreshToken"),
          LoginProvider: "Web",
        })
        .then(function (response) {
          localStorage.setItem("Token", response.data.accessToken);
          localStorage.setItem("RefreshToken", response.data.refreshToken);
          localStorage.setItem("ExpiresAt", response.data.expiresAt);
        })
        .catch(function (error) {
          return Promise.reject(error);
        });
      isAlreadyFetchingAccessToken = false;
      onAccessTokenFetched(localStorage.getItem("Token"));
    }
    return retryOriginalRequest;
  } catch (err) {
    return Promise.reject(err);
  }
}

function onAccessTokenFetched(access_token) {
  subscribers.forEach((callback) => callback(access_token));
  subscribers = [];
}

function addSubscriber(callback) {
  subscribers.push(callback);
}

这篇关于如何使用React Context正确设置Axios拦截器?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-13 04:28