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

Nuxt.js认证中间件异常:自动登出与Toast通知重复问题

问题:Vue.js+Nuxt.js后台管理系统认证中间件异常(自动登出、Toast重复弹出)

我为客户开发了基于Vue.js+Nuxt.js的后台管理系统,后端采用同事开发的Directus。当前认证中间件未按预期工作:登录后将AUTH_TOKEN和REFRESH_TOKEN存储在Cookie中,每5分钟调用API校验token有效性;若返回Token expired则用REFRESH_TOKEN发起刷新请求,更新Cookie中的令牌,失败则跳转至/login页面。但实际使用中出现自动登出、Toast通知重复弹出的问题,相关代码如下:


/middleware/authenticated.js 代码

import authService from '../services/authService';

export default function ({ $cookies, redirect, store, $toast, $router }) {
      const access_token = $cookies.get('access_token');
      const refresh_token = $cookies.get('refresh_token');

      if (!access_token) {
        $cookies.remove('access_token');
        $cookies.remove('refresh_token');
        store.commit('RESET_USER');
        return redirect('/login');
      }
      if(access_token){
        store.commit('SET_USER');
        setInterval(function(){
          try{
              authService.retrieveCurrentUser({headers: {
                "Content-type": "application/json",
                "Authorization": `Bearer ${access_token}`,
              }})
                .then(() => {
                  console.log('ok');
                })
                .catch ((error) => {
                  console.log('prvy catch error', error);
                  if(error.response.data.errors[0].message == 'Token expired.'){
                    const config = {
                        "refresh_token": refresh_token
                    }
                    authService.refreshToken(config)
                    .then((response) => {
                      $cookies.set('access_token', response.data.data.access_token);
                      $cookies.set('refresh_token', response.data.data.refresh_token);
                      store.commit('SET_USER');
                      return redirect();
                    })
                    .catch((err) => {
                      console.log('druhy catch error', err);
                      $cookies.remove('access_token');
                      $cookies.remove('refresh_token');
                      store.commit('RESET_USER');
                      $toast.error('Platnosť vášho prihlásenia vypršala, prihláste sa prosím znova.', { timeout: 5000 });
                      clearInterval(this);
                      return redirect('/login');
                    })
                  }
                })
              }
              catch (err){
                console.log('treti catch error', err);
                $cookies.remove('access_token');
                $cookies.remove('refresh_token');
                store.commit('RESET_USER');
                $toast.error('Platnosť vášho prihlásenia vypršala, prihláste sa prosím znova.', { timeout: 5000 });
                clearInterval(this);
                return redirect('/login');
              }
      }, 300000)
    }
};

authService 代码

import api from '@/services/api';

export default {
    login (credentials){
        return api().post('/auth/login', credentials);
    },
    refreshToken(config) {
        return api().post('/auth/refresh', config);
    },
    logout (refresh_token){
        return api().post('/auth/logout', refresh_token);
    },
    retrieveCurrentUser(refresh_token){
        return api().get('/users/me', refresh_token);
    }
};

页面调用中间件方式

middleware: 'authenticated',

问题根源分析

  1. 定时器重复创建:每次路由进入触发中间件时,都会新建一个5分钟定时器,导致多个定时器同时运行。token过期时,多个定时器会同时触发刷新逻辑,失败后重复弹出Toast并多次执行登出跳转。
  2. token变量未实时更新:定时器内使用的是初始化时获取的access_token,即使刷新后Cookie中的token已更新,定时器仍用旧值发起请求,导致后续校验持续失败,触发自动登出。
  3. 错误处理冗余无效:外层try/catch无法捕获axios异步请求的错误(错误会进入axios的catch回调);同时刷新失败后的定时器清理逻辑不严谨,可能导致定时器残留。
  4. 无意义重定向:刷新token后调用redirect()会重定向到当前页面,再次触发中间件并新建定时器,形成恶性循环。

修复方案

1. 避免重复创建定时器

在全局对象上标记定时器状态,确保每个会话仅创建一个定时器:

// 修改authenticated.js中定时器创建逻辑
if(access_token){
  store.commit('SET_USER');
  // 检查是否已存在定时器,避免重复创建
  if (!window.authCheckTimer) {
    window.authCheckTimer = setInterval(async function(){
      // 后续校验逻辑
    }, 300000)
  }
}

2. 实时获取最新token

每次定时器触发时,从Cookie重新获取token,确保使用最新值:

// 定时器内修改token获取方式
const currentAccessToken = $cookies.get('access_token');
const currentRefreshToken = $cookies.get('refresh_token');

if (!currentAccessToken) {
  // 无有效token直接登出
  clearInterval(window.authCheckTimer);
  window.authCheckTimer = null;
  $cookies.remove('access_token');
  $cookies.remove('refresh_token');
  store.commit('RESET_USER');
  $toast.error('登录已过期,请重新登录', { timeout: 5000 });
  return redirect('/login');
}

// 使用最新token发起校验请求
authService.retrieveCurrentUser({
  headers: {
    "Content-type": "application/json",
    "Authorization": `Bearer ${currentAccessToken}`,
  }
})

3. 简化错误处理并严谨清理定时器

改用async/await简化异步逻辑,确保定时器在登出时被彻底清除:

// 重构定时器内逻辑为async/await
window.authCheckTimer = setInterval(async function(){
  try {
    const currentAccessToken = $cookies.get('access_token');
    const currentRefreshToken = $cookies.get('refresh_token');
    
    if (!currentAccessToken) throw new Error('无有效登录凭证');
    await authService.retrieveCurrentUser({
      headers: {
        "Content-type": "application/json",
        "Authorization": `Bearer ${currentAccessToken}`,
      }
    });
    console.log('token校验通过');
  } catch (error) {
    console.log('token校验失败:', error);
    // 处理token过期场景
    if (error.response?.data?.errors[0]?.message === 'Token expired.') {
      const currentRefreshToken = $cookies.get('refresh_token');
      try {
        const response = await authService.refreshToken({ refresh_token: currentRefreshToken });
        // 更新Cookie和用户状态
        $cookies.set('access_token', response.data.data.access_token);
        $cookies.set('refresh_token', response.data.data.refresh_token);
        store.commit('SET_USER');
      } catch (err) {
        // 刷新失败,执行登出
        clearInterval(window.authCheckTimer);
        window.authCheckTimer = null;
        $cookies.remove('access_token');
        $cookies.remove('refresh_token');
        store.commit('RESET_USER');
        $toast.error('登录已过期,请重新登录', { timeout: 5000 });
        redirect('/login');
      }
    } else {
      // 其他错误,直接登出
      clearInterval(window.authCheckTimer);
      window.authCheckTimer = null;
      $cookies.remove('access_token');
      $cookies.remove('refresh_token');
      store.commit('RESET_USER');
      $toast.error('登录已过期,请重新登录', { timeout: 5000 });
      redirect('/login');
    }
  }
}, 300000)

4. 移除不必要的重定向

刷新token成功后无需调用redirect(),直接更新Cookie和用户状态即可,避免触发中间件重复创建定时器。

5. 统一提示语言

将Toast中的斯洛伐克语提示替换为中文(根据业务需求调整):

$toast.error('登录已过期,请重新登录', { timeout: 5000 });

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 20:12:49