You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);

验证步骤

  1. 登录后查看localStorage,确认同时存在access和refresh令牌
  2. 等待Access Token过期(或手动修改localStorage中的access令牌为无效值),刷新页面,检查是否自动获取新的Access Token并保持认证状态
  3. 登出后确认localStorage中的令牌已被清除

内容的提问来源于stack exchange,提问作者Sight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 07:12:03