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
相关产品推荐
相关产品推荐

