Django+React页面刷新后JWT认证令牌失效问题求助
问题:页面刷新后JWT令牌存在但后端认证失效
我用Django+Django Rest Framework构建后端API,采用DJOSER做认证,借助djangorestframework_simplejwt处理JWT,已在settings.py配置令牌有效期,但页面刷新后,localStorage中存在令牌但后端无法识别,导致认证失效。
前端相关代码
auth.js
import axios from 'axios'; import { LOGIN_SUCCESS, LOGIN_FAIL, USER_LOADED_SUCCESS, USER_LOADED_FAIL, AUTHENTICATED_SUCCESS, AUTHENTICATED_FAIL, LOGOUT } from './types'; export const checkAuthenticated = () => async (dispatch) => { if (localStorage.getItem('access')) { const config = { headers: { 'Content-type': 'application/json', 'Accept': 'application/json', }, }; const body = JSON.stringify({ token: localStorage.getItem('access') }); try { const res = await axios.post( `${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config ); if (res.data.code !== 'token_not_valid') { dispatch({ type: AUTHENTICATED_SUCCESS, }); } else { dispatch({ type: AUTHENTICATED_FAIL, }); } } catch (err) { dispatch({ type: AUTHENTICATED_FAIL, }); } } else { dispatch({ type: AUTHENTICATED_FAIL, }); } }; export const load_user = () => async (dispatch) => { if (localStorage.getItem('access')) { const config = { headers: { 'Content-type': 'application/json', Authorization: `JWT ${localStorage.getItem('access')}`, Accept: 'application/json', }, }; try { const res = await axios.get( `${process.env.REACT_APP_API_URL}/auth/users/me/`, config ); dispatch({ type: USER_LOADED_SUCCESS, payload: res.data, }); } catch (err) { dispatch({ type: USER_LOADED_FAIL, }); } } else { dispatch({ type: USER_LOADED_FAIL, }); } }; export const login = (email, password) => async (dispatch) => { const config = { headers: { 'Content-type': 'application/json', }, }; const body = JSON.stringify({ email, password }); try { const res = await axios.post( `${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config ); localStorage.setItem('access', res.data.access); // Store the access token in local storage dispatch({ type: LOGIN_SUCCESS, payload: res.data, }); dispatch(load_user()); } catch (err) { dispatch({ type: LOGIN_FAIL, }); } }; export const logout = () => dispatch => { dispatch({ type: LOGOUT }) };
Login.js
import React, { useState } from 'react'; import { Link, Navigate, Route } from 'react-router-dom'; import { connect } from 'react-redux'; import { login } from '../actions/auth' import axios from 'axios'; import backGround from '../img/fromAndrea.jpg'; import Home from "../containers/Home" const Login = ({ login, isAuthenticated }) => { const [formData, setFormData] = useState({ email: '', password: '' }); const { email, password } = formData; const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value }); const onSubmit = e => { e.preventDefault(); login(email, password); }; // Redirect home page if (isAuthenticated){ console.log("AUTH success!") return <Navigate to="/" replace={true} /> } return ( Some HTML Code... ) }; const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated }); export default connect(mapStateToProps, { login })(Login);
Home.js
import React, { Fragment, useState } from 'react'; import { connect } from 'react-redux'; import { logout } from '../actions/auth'; import LoggedIn from "./Home/LoggedIn" import NotLoggedIn from "./Home/NotLoggedIn" const Home = ({ logout, isAuthenticated }) => { const [redirect, setRedirect] = useState(false); const guestLinks = () => ( <Fragment> <NotLoggedIn /> </Fragment> ); const authLinks = () => ( <LoggedIn/> ); return( <> <Fragment> {isAuthenticated ? authLinks() : guestLinks()} </Fragment> </> ) }; const mapStateToProps = state => ({ isAuthenticated: state.auth.isAuthenticated }); export default connect(mapStateToProps, { logout })(Home);
后端settings.py配置
SIMPLE_JWT = { 'AUTH_HEADER_TYPES': ('JWT',), 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), 'AUTH_TOKEN_CLASSES': ( 'rest_framework_simplejwt.tokens.AccessToken', ) } DJOSER = { 'LOGIN_FIELD': 'email', 'USER_CREATE_PASSWORD_RETYPE': True, 'USERNAME_CHANGED_EMAIL_CONFIRMATION': True, 'PASSWORD_CHANGED_EMAIL_CONFIRMATION': True, 'SEND_CONFIRMATION_EMAIL': True, 'SET_USERNAME_RETYPE': True, 'SET_PASSWORD_RETYPE': True, 'PASSWORD_RESET_CONFIRM_URL': 'password/reset/confirm/{uid}/{token}', 'USERNAME_RESET_CONFIRM_URL': 'email/reset/confirm/{uid}/{token}', 'ACTIVATION_URL': 'activate/{uid}/{token}', 'SEND_ACTIVATION_EMAIL': True, 'SOCIAL_AUTH_TOKEN_STRATEGY': 'djoser.social.token.jwt.TokenStrategy', 'SOCIAL_AUTH_ALLOWED_REDIRECT_URIS': ['http://localhost:8000/google', 'http://localhost:8000/facebook'], 'SERIALIZERS': { 'user_create': 'accounts.serializers.UserCreateSerializer', 'user': 'accounts.serializers.UserCreateSerializer', 'current_user': 'accounts.serializers.UserCreateSerializer', 'user_delete': 'djoser.serializers.UserDeleteSerializer', } } AUTH_USER_MODEL = 'accounts.UserAccount'
核心问题分析
- 仅存储Access Token,未处理Refresh Token:Access Token过期后无自动刷新机制,页面刷新后直接用过期令牌请求导致认证失败
checkAuthenticated逻辑错误:JWT验证接口/auth/jwt/verify/成功时返回空对象,不存在res.data.code,原判断条件完全失效- Logout未清除本地令牌:登出仅触发状态更新,未清除localStorage中的令牌,易导致状态不一致
解决方案
1. 实现Refresh Token自动刷新机制
修改auth.js,新增刷新令牌的action,并完善登录逻辑存储Refresh Token:
// auth.js 新增refresh_token action export const refresh_token = () => async (dispatch) => { if (localStorage.getItem('refresh')) { const config = { headers: { 'Content-type': 'application/json', }, }; const body = JSON.stringify({ refresh: localStorage.getItem('refresh') }); try { const res = await axios.post( `${process.env.REACT_APP_API_URL}/auth/jwt/refresh/`, body, config ); localStorage.setItem('access', res.data.access); dispatch({ type: LOGIN_SUCCESS, payload: res.data, }); dispatch(load_user()); } catch (err) { dispatch({ type: AUTHENTICATED_FAIL, }); } } else { dispatch({ type: AUTHENTICATED_FAIL, }); } }; // 修改login方法,保存refresh token export const login = (email, password) => async (dispatch) => { const config = { headers: { 'Content-type': 'application/json', }, }; const body = JSON.stringify({ email, password }); try { const res = await axios.post( `${process.env.REACT_APP_API_URL}/auth/jwt/create/`, body, config ); localStorage.setItem('access', res.data.access); localStorage.setItem('refresh', res.data.refresh); // 新增存储refresh token dispatch({ type: LOGIN_SUCCESS, payload: res.data, }); dispatch(load_user()); } catch (err) { dispatch({ type: LOGIN_FAIL, }); } };
2. 修复checkAuthenticated的验证逻辑
调整验证成功/失败的判断逻辑:
export const checkAuthenticated = () => async (dispatch) => { if (localStorage.getItem('access')) { const config = { headers: { 'Content-type': 'application/json', 'Accept': 'application/json', }, }; const body = JSON.stringify({ token: localStorage.getItem('access') }); try { await axios.post( `${process.env.REACT_APP_API_URL}/auth/jwt/verify/`, body, config ); // 验证成功,标记已认证 dispatch({ type: AUTHENTICATED_SUCCESS, }); } catch (err) { // 验证失败,检查是否是令牌过期,尝试刷新 if (err.response?.data?.code === 'token_not_valid') { dispatch(refresh_token()); } else { dispatch({ type: AUTHENTICATED_FAIL, }); } } } else { dispatch({ type: AUTHENTICATED_FAIL, }); } };
3. 完善Logout逻辑,清除本地令牌
export const logout = () => dispatch => { localStorage.removeItem('access'); localStorage.removeItem('refresh'); dispatch({ type: LOGOUT }) };
4. 页面加载时触发认证检查
在根组件(如App.js)中,组件挂载时调用checkAuthenticated:
import { useEffect } from 'react'; import { connect } from 'react-redux'; import { checkAuthenticated } from './actions/auth'; function App({ checkAuthenticated }) { useEffect(() => { checkAuthenticated(); }, [checkAuthenticated]); // 其他组件逻辑 } export default connect(null, { checkAuthenticated })(App);
验证步骤
- 登录后查看localStorage,确认同时存在
access和refresh令牌 - 等待Access Token过期(或手动修改localStorage中的access令牌为无效值),刷新页面,检查是否自动获取新的Access Token并保持认证状态
- 登出后确认localStorage中的令牌已被清除
内容的提问来源于stack exchange,提问作者Sight
相关产品推荐
相关产品推荐

