PAYATHON 2026

Laravel Reverb WebSocket 前端收不到事件,认证返回 403?

排障赵师傅

/broadcasting/auth 返回 403,说明客户端订阅私有频道时未通过授权。订阅没有成功,前端就收不到发往该频道的广播,即使后端已经触发了事件。

从现有代码看,先检查 routes/channels.php 是否注册了 donor.{donorId} 授权规则、文件是否已加载,以及授权时取得的用户是否为目标 donor。仅凭 .env 和 HTTP 路由,还无法确定具体原因。

1. 先补齐私有频道的授权规则

下面的路由接收认证请求,并交给广播组件处理:

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

谁能订阅 donor.123,通常由 routes/channels.php 中的规则决定,这段路由没有定义该权限。

假设 donor 使用 web guard 登录,频道中的 ID 就是当前用户的认证 ID,可以这样配置:

<?php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('donor.{donorId}', function ($user, $donorId) {
    return (string) $user->getAuthIdentifier() === (string) $donorId;
}, ['guards' => ['web']]);

检查时确认以下几点:

  • 规则名称为 donor.{donorId},与事件中的 donor. 前缀一致。
  • 回调明确返回 true 或 false。
  • 当前用户的 ID 与频道里的 donor ID 属于同一种身份。

如果 donor ID 是另一张表的主键,就需要根据用户与 donor 的关联关系授权,不能直接比较两个 ID。

没有匹配的频道规则、回调返回 false,或所选 guard 无法取得用户,都可能导致 403。

确认 routes/channels.php 已加载

即使文件存在,也要检查其中的规则是否已经注册。广播路由和频道文件的注册位置取决于 Laravel 版本及项目结构,常见位置包括 bootstrap/app.php 和 BroadcastServiceProvider。

可以先运行:

php artisan route:list --path=broadcasting -v
php artisan channel:list

如果当前版本没有 channel:list,直接查看项目中的广播注册代码。

确认 /broadcasting/auth 使用了预期的中间件,且 donor.{donorId} 规则已经加载。还要检查框架和手动代码是否注册了同路径的认证路由。保留一套明确的注册方式,避免请求实际执行的路由与预期不同。

2. 区分路由认证与频道授权

auth:web,hospital

表示请求通过 web 或 hospital 中任一 guard 的身份认证即可。具体能否订阅 donor 频道,仍由频道授权规则决定;授权时使用的身份也需要单独确认。

分别核对:

  1. donor 登录时使用哪个 guard。
  2. /broadcasting/auth 请求中,哪些 guard 能取得用户。
  3. 频道规则是否指定了 guards,最终传入回调的用户是谁。

如果 donor 只通过 web 登录,可以让路由和前面的频道规则都使用 web:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Route;

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

这段代码适用于采用手动认证路由的项目。如果框架已注册该端点,应调整现有配置,避免重复添加。

如果医院账号也需要订阅 donor 频道,应按业务关系判断权限,例如医院是否有权查看该 donor 的通知。不同身份表中的 ID 可能重复,不能仅凭两个账号的数字 ID 相同就允许订阅。

3. 检查认证请求是否携带登录会话

即使页面显示“已登录”,也要确认 /broadcasting/auth 收到了同一个会话。

在浏览器 Network 面板中打开失败的请求,检查:

  • 请求是否发往正确的 Laravel 应用。
  • 是否携带登录 Session Cookie。
  • 请求体是否包含 socket_id 和 channel_name。
  • 响应正文来自 Laravel,还是代理或其他中间件。
  • 是否发生过登录重定向。

对于 donor.123,请求中的频道名通常应为:

private-donor.123

使用 Session 登录时,认证路由应经过 web 中间件,访问域名也应保持一致。例如,登录使用 localhost,认证请求却发往 127.0.0.1,可能导致请求没有携带登录 Cookie。

如果前端与 Laravel 跨域,还要检查 Cookie 属性、请求凭据和 CORS;使用 Sanctum 的项目应按其 SPA 认证方式配置。这些会话和跨域问题无法只靠修改一个 Reverb 参数解决。

不同状态码对应的排查方向也有区别:

现象优先检查
403频道授权失败、无法取得频道授权用户,或其他组件拒绝请求
401、跳转登录页路由认证未通过
419Session 或 CSRF 校验
404认证端点路径或路由注册

项目的异常处理可能影响具体响应,需要结合响应正文和 Laravel 日志判断。不要通过关闭 CSRF 校验,或把授权回调改成始终返回 true 来绕过问题。

4. 核对频道名、用户 ID 和事件名

题目中的两种频道写法可以对应:

new PrivateChannel('donor.' . $this->donor->id)
window.Echo.private('donor.123');

Echo 会处理私有频道所需的 private- 前缀,routes/channels.php 中仍写:

Broadcast::channel('donor.{donorId}', /* ... */);

不要把这三个位置都改成 private-donor.*。

还要检查浏览器实际执行的代码。下面的 Blade 表达式只有放在经过 Blade 渲染的模板中,才会替换为用户 ID:

window.Echo.private('donor.{{ auth()->user()->id }}');

如果写在普通 .js 文件中,通常无法得到用户 ID。可以先在 Blade 模板中传入数据,再由前端脚本读取:

<script>
    window.currentDonorId = @json(auth('web')->id());
</script>
window.Echo.private(`donor.${window.currentDonorId}`)
    .listen('.blood-request.created', (data) => {
        console.log(data);
    });

这个示例同样以 donor 使用 web guard 登录为前提。确认事件构造时传入的 $donor->id 与浏览器订阅的 ID 一致。

事件已经定义:

public function broadcastAs(): string
{
    return 'blood-request.created';
}

因此,前端应保留:

.listen('.blood-request.created', ...)

开头的 . 用于监听这个自定义事件名,写法正确。

5. 分开检查 Laravel 认证端点和 Reverb 服务

题目中的两个地址用途不同:

地址用途
http://localhost:8000/broadcasting/authLaravel 验证用户并授权私有频道
localhost:8001Reverb WebSocket 服务

认证请求发往 8000 是合理的,无须因为 WebSocket 使用 8001 就把认证端点也改到 8001。

本机开发时,可以通过启动参数明确指定 Reverb 的监听地址和端口:

php artisan reverb:start --host=127.0.0.1 --port=8001 --debug

使用 reverb:start 启动服务。REVERB_HOST、REVERB_PORT 是 Laravel 向 Reverb 发送广播时使用的连接配置;Reverb 的服务监听地址由 REVERB_SERVER_HOST、REVERB_SERVER_PORT 或启动参数控制,两处需要分别核对。

还要确认:

  • Echo 初始化代码实际读取了 VITE_REVERB_*。
  • Laravel、Reverb 和前端使用的应用配置相互对应。
  • Reverb 的 allowed_origins 允许当前页面来源。
  • 浏览器能访问配置中的 WebSocket 主机和端口。

如果浏览器运行在另一台设备上,localhost 指向的是那台设备,需要换成浏览器能够访问的服务地址。

修改配置后,可根据实际变更执行:

php artisan config:clear

修改 VITE_* 后,需要重启 Vite 开发服务或重新构建前端资源;修改 Reverb 配置后,需要重启 Reverb 进程。REVERB_APP_SECRET 只应保留在服务端。

排查 /broadcasting/auth 的 403 时,先检查 Laravel 的身份认证和频道授权,再验证这些连接设置。

6. 授权成功后,再检查广播队列

BloodRequestCreated 实现了 ShouldBroadcast。如果使用异步队列连接,触发事件后还需要队列 worker 执行广播任务:

php artisan queue:work

队列连接为 sync 时,无须额外启动 worker。使用异步队列时,还要查看失败任务和 worker 日志,确认广播任务已经成功发送给 Reverb,不能仅凭事件已触发来判断。

修复后,按以下顺序验证:

  1. WebSocket 连接建立。
  2. /broadcasting/auth 返回成功的授权响应。
  3. WebSocket 中出现该频道的 pusher_internal:subscription_succeeded 消息。
  4. 在订阅成功后重新触发事件。
  5. 检查广播任务执行情况、Reverb 调试输出及浏览器收到的帧。

私有频道订阅成功前发出的广播,通常不会在授权修复后自动补发。每次验证都应等订阅成功后,再触发一个新事件。

备注:内容仅供参考。