我创建了一个自定义钩子并在其中创建了一个 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,它调用自定义挂钩,但仅在访问令牌存在之前进行编辑和删除,其余时间都有效,所以我的新令牌没有更新
您的问题是,当您获取新令牌时,此拦截器范围内的
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);
}
);
您正在
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
状态都会更新,并且更新后的状态将从钩子返回。这应该确保您的应用程序在刷新后使用最新的令牌。