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

Nuxt3集成Laravel-Echo多私有频道后刷新登出问题排查

Nuxt3 集成 Laravel-Echo + Sanctum 多私有频道订阅后刷新登出问题

已实现配置

使用nuxt-auth-sanctum完成认证,相关配置如下:

nuxt.conf.ts配置

// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
  modules: [
    '@nuxt/content',
    '@vueuse/nuxt',
    'nuxt-auth-sanctum'
  ],
  runtimeConfig: {
    backendUrl: process.env.NUXT_BACKEND_URL,
    public: {
      pusherKey: process.env.NUXT_PUBLIC_PUSHER_KEY,
      pusherHost: process.env.NUXT_PUBLIC_PUSHER_HOST,
      pusherWsPort: process.env.NUXT_PUBLIC_PUSHER_WS_PORT,
      pusherWssPort: process.env.NUXT_PUBLIC_PUSHER_WSS_PORT,
      pusherScheme: process.env.NUXT_PUBLIC_PUSHER_SCHEME,
      pusherCluster: process.env.NUXT_PUBLIC_PUSHER_CLUSTER
    }
  },
  sanctum: {
    baseUrl: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/api/${process.env.NUXT_API_VERSION}`,
    origin: process.env.NUXT_PUBLIC_SITE_URL,
    endpoints: {
      csrf: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/csrf`,
      login: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/auth/login`,
      logout: `${process.env.NUXT_PUBLIC_SITE_URL}/backend/auth/logout`,
      user: '/auth/me'
    },
    logLevel: 4,
    redirect: {
      keepRequestedRoute: true,
      onLogin: '/dashboard',
      onLogout: '/',
      onAuthOnly: '/login',
      onGuestOnly: '/dashboard'
    }
  },
  routeRules: {
    '/backend/api/v1/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/api/${process.env.NUXT_API_VERSION}/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/web/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/auth/**': {
      proxy: {
        to: `${process.env.NUXT_BACKEND_URL}/auth/**`,
        headers: { accept: 'application/json' }
      }
    },
    '/backend/csrf': {
      proxy: `${process.env.NUXT_BACKEND_URL}/sanctum/csrf-cookie`,
      headers: { accept: 'application/json' }
    },
    '/broadcasting/auth': {
      proxy: `${process.env.NUXT_BACKEND_URL}/api/${process.env.NUXT_API_VERSION}/broadcasting/auth`,
      headers: { accept: 'application/json' }
    },
    '/api/search.json': { prerender: true },
    '/docs': { redirect: '/docs/getting-started', prerender: false }
  },
  devtools: {
    enabled: true
  },
})

echo.client.ts插件代码

import Echo from 'laravel-echo'
import Pusher from 'pusher-js'
import {
  useCookie
} from '#app'

declare global {
  interface Window {
    pusher: any
  }
}

export default defineNuxtPlugin(() => {
  window.pusher = Pusher
  const config = useRuntimeConfig().public
  const token = useCookie('XSRF-TOKEN')

  const echo = new Echo({
    broadcaster: 'pusher',
    key: config.pusherKey,
    wsHost: config.pusherHost,
    wsPort: config.pusherWsPort,
    wssPort: config.pusherWssPort,
    forceTLS: (config.pusherScheme ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
    cluster: config.pusherCluster,
    auth: {
      headers: {
        'X-XSRF-TOKEN': token.value
      }
    }
  })

  return {
    provide: {
      echo: echo
    }
  }
})

单私有频道订阅时功能正常,广播认证请求示例:

fetch("http://web.example.test/broadcasting/auth", {
  "headers": {
    "accept": "*/*",
    "accept-language": "de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7",
    "content-type": "application/x-www-form-urlencoded",
    "x-xsrf-token": "..."
  },
  "referrer": "http://web.example.test/orders",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": "socket_id=6189903872.1771283099&channel_name=private-Order.9c571400-9c2e-4d8a-bdbb-088ea138b0d5",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

问题描述

同时订阅两个私有频道时,订阅过程正常,但刷新页面后会自动登出。移除任意一个$echo.private()语句,或改用公开频道$echo.channel()则无此问题。

查看Laravel数据库sessions表发现,刷新后会创建一条未关联user_id的新会话。排查StartSession中间件后确认:用户信息接口请求与第一个广播认证请求共享会话,但第二个广播认证请求会创建新会话。

排查与解决方案

1. 确保CSRF令牌实时有效

第二个广播认证请求可能复用了过期令牌,修改Echo插件,动态获取最新XSRF令牌:

export default defineNuxtPlugin(async () => {
  window.pusher = Pusher
  const config = useRuntimeConfig().public
  
  // 提前获取最新CSRF令牌
  await useFetch('/backend/csrf')
  const token = useCookie('XSRF-TOKEN')

  const echo = new Echo({
    broadcaster: 'pusher',
    key: config.pusherKey,
    wsHost: config.pusherHost,
    wsPort: config.pusherWsPort,
    wssPort: config.pusherWssPort,
    forceTLS: (config.pusherScheme ?? 'https') === 'https',
    enabledTransports: ['ws', 'wss'],
    cluster: config.pusherCluster,
    auth: {
      // 动态返回令牌,确保每次请求用最新值
      headers: () => ({
        'X-XSRF-TOKEN': useCookie('XSRF-TOKEN').value
      })
    }
  })

  return {
    provide: {
      echo: echo
    }
  }
})

2. 检查Laravel广播认证路由中间件

确保广播认证路由使用包含会话和CSRF保护的web中间件组:

// routes/channels.php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Broadcast;

Route::post('/broadcasting/auth', function (Request $request) {
    return Broadcast::auth($request);
})->middleware('web');

3. 串行处理频道订阅请求

并行请求可能引发会话存储冲突,改为串行订阅:

onMounted(async () => {
  currentProject.value = data.value.data[selectedProject.value]
  
  // 先订阅第一个频道,确认连接后再订阅第二个
  await new Promise(resolve => {
    $echo.private(`User.${user.value.uuid}`)
      .listen('.OrderCreated', () => refresh())
      .connected(resolve)
  })

  await new Promise(resolve => {
    $echo.private(`Order.${currentProject.value.uuid}`)
      .listen('.OrderUpdated', () => refresh())
      .connected(resolve)
  })
})

4. 完善Nuxt代理的Credentials配置

为广播认证路由添加credentials: true,确保会话Cookie正确传递:

// nuxt.conf.ts -> routeRules
'/broadcasting/auth': {
  proxy: {
    to: `${process.env.NUXT_BACKEND_URL}/api/${process.env.NUXT_API_VERSION}/broadcasting/auth`,
    headers: { accept: 'application/json' },
    credentials: true
  }
},

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:37:02