由于我想用React Context设置Axios拦截器,因此似乎可行的唯一解决方案是创建一个Interceptor组件,以便使用useContext挂钩访问Context状态并进行调度。
问题是,这将创建一个闭包,并在调用时将旧数据返回拦截器。
我正在使用React / Node进行JWT身份验证,并且正在使用上下文API存储访问令牌。
这是我的拦截器组件现在的样子:
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
函数。
此问题仍然存在的地方是interceptTraffic
函数。
它由一个请求拦截器和一个响应拦截器组成,该请求拦截器将带有访问令牌的标头附加到每个请求中,该响应拦截器用于处理访问令牌的到期,以便使用刷新令牌来获取新令牌。
您会注意到我正在导出ax
(Axios
的一个实例,我在其中添加了拦截器),但是在该组件之外调用它时,由于关闭,它引用了旧的商店数据。
这显然不是一个好的解决方案,但这就是为什么我需要帮助组织拦截器同时仍能够访问Context数据的原因。
请注意,我将此组件创建为包装器,因为它呈现了提供给它的子组件,这是App的主要组件。
感谢您的帮助。
答案 0 :(得分:3)
将 JWT 存储在 localStorage 中是一种常见的做法
localStorage.setItem('token', 'your_jwt_eykdfjkdf...');
在登录或页面刷新时,创建一个模块,导出带有附加令牌的 Axios 实例。我们将从 localStorage 获取令牌
custom-axios.js
import axios from 'axios';
// axios instance for making requests
const axiosInstance = axios.create();
// request interceptor for adding token
axiosInstance.interceptors.request.use((config) => {
// add token to request headers
config.headers['Authorization'] = localStorage.getItem('token');
return config;
});
export default axiosInstance;
然后,只需导入我们刚刚创建的 Axios 实例并发出请求即可。
import axios from './custom-axios';
axios.get('/url');
axios.post('/url', { message: 'hello' });
如果您将 JWT 存储在 state 中,或者您可以从 state 中获取新令牌,请创建一个模块,该模块导出一个函数,该函数将令牌作为参数并返回一个带有附加令牌的 axios 实例,如下所示:< /p>
custom-axios.js
import axios from 'axios';
const customAxios = (token) => {
// axios instance for making requests
const axiosInstance = axios.create();
// request interceptor for adding token
axiosInstance.interceptors.request.use((config) => {
// add token to request headers
config.headers['Authorization'] = token;
return config;
});
return axiosInstance;
};
export default customAxios;
然后导入我们刚刚创建的函数,从 state 中获取 token,并发出请求:
import axios from './custom-axios';
// logic to get token from state (it may vary from your approach but the idea is same)
const token = useSelector(token => token);
axios(token).get('/url');
axios(token).post('/url', { message: 'hello' });
答案 1 :(得分:0)
我有一个模板,可以在每天有数百万访问量的系统中使用。
这解决了我的刷新令牌问题并重新尝试请求而不会崩溃
首先,我有一个包含 axios、配置、地址、标题的“api.js”。 在这个文件中有两种方法,一种有 auth ,另一种没有。 在同一个文件中,我配置了我的拦截器:
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 方法。我把它放在另一个文件里,但你可以把它放在任何你想要的地方:
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);
}