axios拦截器问题react redux和node

问题描述 投票:0回答:2

我创建了一个自定义钩子并在其中创建了一个 axios 实例,用于在访问令牌过期时获取新令牌,最初在页面上加载令牌似乎没问题,但在 timex 过期后,我的令牌没有设置,我的令牌出了什么问题代码.

这是我的 useAxiosInstance.js 文件

import axios from "axios";
import { jwtDecode } from "jwt-decode";
import { useDispatch } from "react-redux";
import { authSuccess } from "../redux/auth/authSlice";
let userData = localStorage.getItem("userData")
  ? JSON.parse(localStorage.getItem("userData"))
  : null;
export const axiosInstance = axios.create({
  baseURL: process.env.REACT_APP_API_URL,
  headers: {
    Authorization: userData?.accessToken
      ? `Bearer ${userData.accessToken}`
      : "",
  },
});

const useAxiosInstance = () => {
  const getTokens = async (data) => {
    try {
      const res = await axios.post(
        `${process.env.REACT_APP_API_URL}refresh`,
        data,
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      return res.data;
    } catch (err) {
      return err;
    }
  };

  axiosInstance.interceptors.request.use(
    async (config) => {
      const accessTokenExpiryDate = jwtDecode(userData?.accessToken);

      console.log(accessTokenExpiryDate,"ex")

      if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) {
        const { accessToken, refreshToken } = await getTokens(
          JSON.stringify({ token: userData.refreshToken })
        );

        userData = {
          ...userData,
          accessToken,
          refreshToken,
        };
        localStorage.setItem("userData", JSON.stringify(userData));
        config.headers["Authorization"] = "Bearer " + accessToken;
      }
      return config;
    },
    (error) => Promise.reject(error)
  );

  return userData;
};

export default useAxiosInstance;

这是我的 app.js 文件

import { useDispatch, useSelector } from "react-redux";
import "./app.less";
import Header from "./components/header/Header";
import { BrowserRouter } from "react-router-dom";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { useEffect } from "react";
import { authSuccess } from "./redux/auth/authSlice";
import AppRoute from "./routes/AppRoute";
import useAxiosInstance from "./utilities/useAxiosInstance"

function App() {
  const theme = useSelector((state) => state.theme);
  const auth = useSelector((state) => state.auth);
  const dispatch = useDispatch();

  const userData = useAxiosInstance();

  useEffect(() => {
    dispatch(authSuccess(userData));
  }, [userData, dispatch]);


  return (
    <BrowserRouter>
      <div
        className={theme.mode === "dark" ? "dark-mode app" : "light-mode app"}
      >
        {auth?.data && <Header />}
        <ToastContainer />
        <AppRoute />
      </div>
    </BrowserRouter>
  );
}

export default App;

这是我的身份验证切片文件

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  data: null,
};

const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {
    authSuccess: (state, action) => {
      state.data =action.payload
    },
    authLogout: (state, action) => {
      state.data = null;
    },
  },
});

export const { authSuccess, authLogout } = authSlice.actions;

export default authSlice.reducer;

这是我的用户切片文件

import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { showToast } from "../../utilities/toast";
import { authLogout } from "../auth/authSlice";
import { axiosInstance } from "../../utilities/useAxiosInstance";



const initialState = {
  update: { loading: false, data: null, error: null },
  get: { loading: false, data: null, error: null },
  delete: { loading: false, data: null, error: null },
};

export const getUser = createAsyncThunk("user/getUser", (payload) => {
  const { data, mode } = payload;
  return axiosInstance
    .get(`/user/${data?._id}`, {
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + data.accessToken,
      },
    })
    .then((res) => res.data)
    .catch((error) => {
      showToast(error.response.data.message, mode, "error");
      throw error.response.data.message;
    });
});

export const deleteUser = createAsyncThunk("user/deleteUser", (payload) => {
  const { auth, navigate, mode, dispatch } = payload;

  return axiosInstance
    .delete(`/user/${auth._id}`, {
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + auth.accessToken,
      },
    })
    .then((res) => {
      showToast("Deleted Successfully!", mode, "success");
      dispatch(authLogout());
      localStorage.removeItem("userData");
      navigate("/");
    })
    .catch((err) => {
      showToast(err?.response?.data?.message, mode, "error");
      throw err.response.data.message;
    });
});

export const editUser = createAsyncThunk("user/editUser", (payload) => {
  const { form, data, mode } = payload;

  return axiosInstance
    .put(`/user/${data._id}`, form, {
      headers: {
        "Content-Type": "multipart/form-data",
        // Authorization: "Bearer " + data.accessToken,
      },
    })
    .then((res) => {
      showToast("Updated Successfully!", mode, "success");
    })
    .catch((err) => {
      showToast(err?.response?.data?.message, mode, "error");
      throw err.response.data.message;
    });
});

const userSlice = createSlice({
  name: "user",
  initialState,
  extraReducers: (builder) => {
    builder.addCase(getUser.pending, (state, action) => {
      state.get.loading = true;
    });
    builder.addCase(getUser.fulfilled, (state, action) => {
      state.get.loading = false;
      state.get.data = action.payload;
    });
    builder.addCase(getUser.rejected, (state, action) => {
      state.get.loading = false;
      state.get.error = action.payload;
    });
    builder.addCase(editUser.pending, (state, action) => {
      state.update.loading = true;
    });
    builder.addCase(editUser.fulfilled, (state, action) => {
      state.update.loading = false;
      state.update.data = action.payload;
    });
    builder.addCase(editUser.rejected, (state, action) => {
      state.update.loading = false;
      state.update.error = action.payload;
    });
    builder.addCase(deleteUser.pending, (state, action) => {
      state.delete.loading = true;
    });
    builder.addCase(deleteUser.fulfilled, (state, action) => {
      state.delete.loading = false;
      state.delete.data = action.payload;
    });
    builder.addCase(deleteUser.rejected, (state, action) => {
      state.delete.loading = false;
      state.delete.error = action.payload;
    });
  },
});

export default userSlice.reducer;

由于我在页面加载时调用 getUser api,它调用自定义挂钩,但仅在访问令牌存在之前进行编辑和删除,其余时间都有效,所以我的新令牌没有更新

javascript reactjs axios react-redux redux-toolkit
2个回答
0
投票

您的问题是,当您获取新令牌时,此拦截器范围内的

userData
不会自动更新,因为它是在拦截器函数之外设置的。这可能会导致旧令牌被重复使用,从而导致您遇到的问题。

您可以按如下方式修改您的拦截器,

axiosInstance.interceptors.request.use(
  async (config) => {
    // Retrieve the latest userData from localStorage
    let currentUserData = localStorage.getItem("userData")
      ? JSON.parse(localStorage.getItem("userData"))
      : null;

    // Decode the token to check its expiration
    const accessTokenExpiryDate = jwtDecode(currentUserData?.accessToken);

    // If the token has expired, refresh it
    if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) {
      const { accessToken, refreshToken } = await getTokens(
        JSON.stringify({ token: currentUserData.refreshToken })
      );

      // Update userData with the new tokens
      currentUserData = {
        ...currentUserData,
        accessToken,
        refreshToken,
      };

      // Save the updated userData to localStorage
      localStorage.setItem("userData", JSON.stringify(currentUserData));

      // Update the Authorization header with the new token
      config.headers["Authorization"] = "Bearer " + accessToken;
    } else {
      // If the token hasn't expired, use the current token
      config.headers["Authorization"] = "Bearer " + currentUserData.accessToken;
    }
    
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

0
投票

您正在

useAxiosInstance
挂钩中管理和更新用户数据(包括访问令牌)。让我们回顾一下您的代码:

const useAxiosInstance = () => {
  const getTokens = async (data) => {
    try {
      const res = await axios.post(
        `${process.env.REACT_APP_API_URL}refresh`,
        data,
        {
          headers: {
            "Content-Type": "application/json",
          },
        }
      );
      return res.data;
    } catch (err) {
      return err;
    }
  };

  axiosInstance.interceptors.request.use(
    async (config) => {
      const accessTokenExpiryDate = jwtDecode(userData?.accessToken);

      if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) {
        const { accessToken, refreshToken } = await getTokens(
          JSON.stringify({ token: userData.refreshToken })
        );

        userData = {
          ...userData,
          accessToken,
          refreshToken,
        };
        localStorage.setItem("userData", JSON.stringify(userData));
        config.headers["Authorization"] = "Bearer " + accessToken;
      }
      return config;
    },
    (error) => Promise.reject(error)
  );

  return userData;
};

问题可能与您直接从挂钩返回userData

这一事实有关,并且刷新令牌时它可能不会更新。当钩子初始化时,仅向 
userData
 分配一次从本地 
storage 检索到的初始值。对
拦截器
内的 userData 的后续更新可能不会反映在返回值中。

要解决此问题,您应该更新挂钩内的状态并返回该状态。以下是修改钩子的方法:

import { useState } from 'react'; const useAxiosInstance = () => { const [userData, setUserData] = useState(localStorage.getItem("userData") ? JSON.parse(localStorage.getItem("userData")) : null); const getTokens = async (data) => { try { const res = await axios.post( `${process.env.REACT_APP_API_URL}refresh`, data, { headers: { "Content-Type": "application/json", }, } ); return res.data; } catch (err) { return err; } }; axiosInstance.interceptors.request.use( async (config) => { const accessTokenExpiryDate = jwtDecode(userData?.accessToken); if (accessTokenExpiryDate.exp * 1000 < new Date().getTime()) { const { accessToken, refreshToken } = await getTokens( JSON.stringify({ token: userData.refreshToken }) ); const updatedUserData = { ...userData, accessToken, refreshToken, }; localStorage.setItem("userData", JSON.stringify(updatedUserData)); setUserData(updatedUserData); config.headers["Authorization"] = "Bearer " + accessToken; } return config; }, (error) => Promise.reject(error) ); return userData; };
通过此更改,每当刷新令牌时,

userData

状态都会更新,并且更新后的状态将从钩子返回。这应该确保您的
应用程序在刷新后使用最新的令牌

© www.soinside.com 2019 - 2024. All rights reserved.