React首次请求遇401 Unauthorized错误,刷新后恢复问题排查
所有请求首次发起时均返回401错误,但刷新页面后问题消失。已确认token已正确获取并存入sessionStorage,但首次请求时token未注入请求头。
Axios配置代码
import axios from "axios"; import { api } from "../config"; // default axios.defaults.baseURL = api.API_URL; // content type const token = JSON.parse(sessionStorage.getItem('authUser')) ? JSON.parse(sessionStorage.getItem('authUser')).data.token : null; if (token) axios.defaults.headers.common["Authorization"] = 'Bearer ' + token; // content type axios.defaults.headers.post["Content-Type"] = 'application/x-www-form-urlencoded'; // intercepting to capture errors axios.interceptors.response.use( function (response) { return response ? response.data : response; }, function (error) { // Any status codes that falls outside the range of 2xx cause this function to trigger let message; switch (error.response.status) { case 500: message = "Internal Server Error"; break; case 401: window.location.href = '/login'; message = "not"; break; case 404: message = "Sorry! the data you are looking for could not be found"; break; default: message = error.response.data.message; } return Promise.reject(message); } ); /** * Sets the default authorization * @param {*} token */ const setAuthorization = (token) => { axios.defaults.headers.common["Authorization"] = "Bearer " + token; }; class APIClient { /** * Fetches data from given url */ get = (url, params) => { let response; let paramKeys = []; if (params) { Object.keys(params).map((key) => { paramKeys.push(key + "=" + params[key]); return paramKeys; }); const queryString = paramKeys && paramKeys.length ? paramKeys.join("&") : ""; response = axios.get(`${url}?${queryString}`, params); } else { response = axios.get(`${url}`, params); } return response; }; /** * post given data to url */ create = (url, data) => { return axios.post(url, data); }; /** * Updates data */ update = (url, data) => { return axios.patch(url, data); }; put = (url, data) => { return axios.put(url, data); }; /** * Delete */ delete = (url, config) => { return axios.delete(url, { ...config }); }; } const getLoggedinUser = () => { const user = sessionStorage.getItem("authUser"); if (!user) { return null; } else { return JSON.parse(user); } }; export { APIClient, setAuthorization, getLoggedinUser };
登录Saga代码
import { call, put, takeEvery, takeLatest } from "redux-saga/effects"; // Login Redux States import { LOGIN_USER, LOGOUT_USER, SOCIAL_LOGIN } from "./actionTypes"; import { apiError, loginSuccess, logoutUserSuccess } from "./actions"; //Include Both Helper File with needed methods import { getFirebaseBackend } from "../../../helpers/firebase_helper"; import { postJwtLogout, postJwtLogin, postSocialLogin, } from "../../../helpers/backend_helper"; import { toast } from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; const fireBaseBackend = getFirebaseBackend(); function* loginUser({ payload: { user, history } }) { try { if (process.env.REACT_APP_DEFAULTAUTH === "firebase") { const response = yield call( fireBaseBackend.loginUser, user.email, user.password ); yield put(loginSuccess(response)); } else if (process.env.REACT_APP_DEFAULTAUTH === "jwt") { const response = yield call(postJwtLogin, { username: user.email, password: user.password, }); sessionStorage.setItem("authUser", JSON.stringify(response)); if (response.status === "SUCCESS") { yield put(loginSuccess(response)); history.push("/dashboard"); window.location.reload(); } else { yield put(apiError(response)); } } } catch (error) { yield put(apiError(error)); toast.error(error, { autoClose: 6000 }); } } function* logoutUser() { try { sessionStorage.removeItem("authUser"); if (process.env.REACT_APP_DEFAULTAUTH === "jwt") { const response = yield call(postJwtLogout); yield put(logoutUserSuccess(LOGOUT_USER, response)); } else { yield put(logoutUserSuccess(LOGOUT_USER, true)); } } catch (error) { yield put(apiError(LOGOUT_USER, error)); } } function* socialLogin({ payload: { data, history, type } }) { try { if (process.env.REACT_APP_DEFAULTAUTH === "firebase") { const fireBaseBackend = getFirebaseBackend(); const response = yield call(fireBaseBackend.socialLoginUser, data, type); sessionStorage.setItem("authUser", JSON.stringify(response)); yield put(loginSuccess(response)); } else { const response = yield call(postSocialLogin, data); sessionStorage.setItem("authUser", JSON.stringify(response)); yield put(loginSuccess(response)); } history.push("/dashboard"); } catch (error) { yield put(apiError(error)); } } function* authSaga() { yield takeEvery(LOGIN_USER, loginUser); yield takeLatest(SOCIAL_LOGIN, socialLogin); yield takeEvery(LOGOUT_USER, logoutUser); } export default authSaga;
问题原因及解决方法
原因
Axios配置中,Authorization请求头是在模块加载时一次性读取sessionStorage设置的。登录成功后虽将token存入sessionStorage,但此时Axios的默认请求头已初始化完成,不会自动更新,导致首次进入页面时请求未携带token,触发401。刷新页面后模块重新加载,读取到最新token,请求头设置成功,问题消失。
解决方法
方法1:登录成功后手动更新请求头
在登录Saga的loginUser函数中,存入sessionStorage后立即调用setAuthorization方法更新Axios请求头,无需刷新页面:
- 首先在登录Saga中导入
setAuthorization:
import { setAuthorization } from "../../path-to-your-axios-config-file";
- 修改jwt登录分支代码:
else if (process.env.REACT_APP_DEFAULTAUTH === "jwt") { const response = yield call(postJwtLogin, { username: user.email, password: user.password, }); sessionStorage.setItem("authUser", JSON.stringify(response)); if (response.status === "SUCCESS") { // 新增:调用setAuthorization更新请求头 yield call(setAuthorization, response.data.token); yield put(loginSuccess(response)); history.push("/dashboard"); // 移除window.location.reload(); } else { yield put(apiError(response)); } }
方法2:使用请求拦截器动态添加token
修改Axios配置,添加请求拦截器,每次发起请求时实时从sessionStorage读取token并设置请求头:
// 在Axios配置中添加请求拦截器 axios.interceptors.request.use( function(config) { const token = JSON.parse(sessionStorage.getItem('authUser'))?.data.token; if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, function(error) { return Promise.reject(error); } );
同时可以移除模块加载时设置请求头的代码:
// 移除以下代码 // const token = JSON.parse(sessionStorage.getItem('authUser')) // ? JSON.parse(sessionStorage.getItem('authUser')).data.token : null; // // if (token) axios.defaults.headers.common["Authorization"] = 'Bearer ' + token;
内容的提问来源于stack exchange,提问作者Serija Nada
相关产品推荐
相关产品推荐

