Vue3 SPA + Laravel Sanctum广播路由授权401问题排查
Vue3 SPA + Laravel Sanctum 私有频道广播401错误
正在尝试为Vue3 SPA搭配Laravel Sanctum后端配置私有频道广播。公开频道(无需授权)可正常运行,但访问私有频道所需的Sanctum保护路由时,客户端收到401错误。使用原生pusher-js库和laravel-echo均出现该问题,已调试多时未解决。
后端配置
/routes/api.php
Route::middleware('auth:sanctum')->group(function () { Route::post('/broadcasting/auth', function (Request $request) { // 401错误导致此代码无法执行 \Log::info('hello'); response()->json('hello'); }); });
/app/Events/ChatMessageSent.php
<?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class ChatMessageSent implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $chatSessionId; public $chatMessage; public function __construct($chatSessionId, $chatMessage) { $this->chatSessionId = $chatSessionId; $this->chatMessage = $chatMessage; } public function broadcastOn() { return new PrivateChannel('chat.'.$this->chatSessionId); } }
ChatController.php中的调用方式:
event(new ChatMessageSent($session->id, $message));
BroadcastServiceProvider.php
<?php namespace App\Providers; use Illuminate\Support\Facades\Broadcast; use Illuminate\Support\ServiceProvider; class BroadcastServiceProvider extends ServiceProvider { public function boot() { Broadcast::routes(['middleware' => ['api', 'auth:sanctum']]); require base_path('routes/channels.php'); } }
/routes/channels.php
<?php use App\Models\User; use Illuminate\Support\Facades\Broadcast; use App\Models\ChatSession; Broadcast::channel('chat.{chatSessionId}', function ($user, $chatSessionId) { $chatSession = ChatSession::find($chatSessionId); return $chatSession ? $chatSession->hasParticipant($user) : false; });
VerifyCsrfToken.php
<?php namespace App\Http\Middleware; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken as Middleware; class VerifyCsrfToken extends Middleware { protected $except = [ '/api/pusher/auth', '/api/broadcasting/auth', ]; }
已在/config/app.php中启用App\Providers\BroadcastServiceProvider::class。
前端配置
login.vue
const signIn = () => { axios.get('/sanctum/csrf-cookie').then((res) => { store.authToken = res.config.headers['X-XSRF-TOKEN'] console.log(store.authToken) axios .post('/login', form) .then(() => { store.auth = sessionStorage.auth = 1 store.signInModal = false }) .catch((er) => { state.errors = er.response.data.errors }) }) }
返回的XSRF-Token:
eyJpdiI6Imp0dEc5ck9NU1BZQUlqbUlxYVRoY3c9PSIsInZhbHVlIjoiSUJBNXNJVTg3OTFTN0c2VXluQzJ0Vk5sVGxnenU2aVhNWGE1V2tEMGFBNVBabmZDOFpqRWF6MFN0N0VLUDVRVmtXVFlmOTQ1MlcySFppWUM5eGZRcU1pSDlsN0grVCtRV2ZjYW1ONjFEdDQzT0JOUW4xalkwOGhaWGVXQi8rbHgiLCJtYWMiOiIyZjUyNGEzZTY5MmRiZDgyYzVhZDMyNGJhMjQ3NGFmYmE5ZDZhNDc0NDZiYTY3NTQ4MzFkYWZmMjU1YWNjNTkwIiwidGFnIjoiIn0=
chat.vue(调用广播路由)
import initEcho from '@/lib/laravel-echo.js' const inquireChatSession = () => { axios .get(`/api/chat/${props.id}`) .then((res) => { state.chatSessionId = res.data.id state.messages = res.data.messages state.loadingSession = false const echo = initEcho(store.authToken) // 创建laravel-echo实例并传入authToken,尝试调用authEndpoint(/api/broadcasting/auth)授权时返回401 echo .private(`private-chat.${state.chatSessionId}`) .listen('ChatMessageSent', (data) => { state.messages.push(data.chatMessage) if (state.focusBrowserTab) { state.messages.forEach((message) => { if (message.sender_id !== store.user.id) { message.read_status = 1 } }) } }) }) .catch((er) => { state.errors = er.response?.data.errors state.loadingSession = false }) }
laravel-echo.js
import Echo from 'laravel-echo' import Pusher from 'pusher-js' window.Pusher = Pusher export default (token) => { return (window.Echo = new Echo({ broadcaster: 'pusher', key: 'bf29be46d8eb2ea8ccd4', cluster: 'eu', forceTLS: true, wsPort: 443, wssPort: 443, enabledTransports: ['ws', 'wss'], withCredentials: true, authEndpoint: 'http://localhost:8000/api/broadcasting/auth', auth: { headers: { 'X-Requested-With': 'XMLHttpRequest', Authorization: `Bearer ${token}`, }, }, })) }
axios.js
import axios from 'axios' axios.defaults.withCredentials = true if (import.meta.env.DEV) { axios.defaults.baseURL = 'http://localhost:8000' }
内容的提问来源于stack exchange,提问作者Artur Müller Romanov
相关产品推荐
相关产品推荐

