avatar

mdo

Hello

  • 首页
  • 知识库
  • 归档
  • 标签
  • 关于
主页 通过监听 request 事件来拦截和验证 WebSocket 握手
文章

通过监听 request 事件来拦截和验证 WebSocket 握手

发表于 2026-06-18 更新于 2026-06- 18
作者 mdo
9~12 分钟 阅读

在 think-swoole 中,如果项目同时开启了 HTTP 服务和 WebSocket 服务,客户端发起 WebSocket 握手时,底层的 Swoole 会优先将其视为一个普通的 HTTP 请求。

如果你直接监听底层的 handshake 事件感到协议计算太繁琐,完全可以通过监听 request 事件来拦截和验证 WebSocket 握手。这种方式更加框架层(Framework-friendly),可以直接利用 ThinkPHP 的 Request 对象,甚至能完美兼容系统的中间件(Middleware)。

以下是具体的实现方案:

🛠️ 核心原理:在 request 事件中识别 Upgrade

当客户端发起 ws:// 连接时,HTTP 请求头中会包含 Upgrade: websocket。我们只需要在 request 事件中拦截这种请求,进行 Token 验证:

  • 验证通过:不作干预,让底层自动放行并升级为 WebSocket。

  • 验证失败:直接通过 HTTP 响应返回错误状态码(如 401),提前熔断。


📝 实现步骤

1. 创建全局或路由中间件(推荐)

在 think-swoole 中,request 事件最终会被路由到 ThinkPHP 的应用层。因此,直接使用 ThinkPHP 的 中间件 是最优雅的拦截方式。

使用命令行生成一个中间件:

bash

php think make:middleware WsAuth

请谨慎使用此类代码。

2. 编写中间件验证逻辑

打开 app\middleware\WsAuth.php,编写如下逻辑:

php

namespace app\middleware;

use Closure;
use think\Request;
use think\Response;

class WsAuth
{
    public function handle(Request $request, Closure $next)
    {
        // 1. 判断当前请求是否是 WebSocket 升级握手请求
        $isUpgrade = strtolower($request->header('upgrade')) === 'websocket';

        if ($isUpgrade) {
            // 2. 获取客户端带来的 Token(支持从 URL、Header 或 Cookie 中获取)
            $token = $request->param('token') ?: $request->header('Authorization');

            // 3. 执行你的验证逻辑
            if (!$this->checkToken($token)) {
                // 验证失败:直接返回 401 响应,阻止连接升级
                return response('Unauthorized: Handshake Failed', 401);
            }
            
            // 【可选】验证成功:可以将解密出的用户 UID 注入到当前的 Request 对象中
            // 方便后面的连接建立事件或消息事件读取
            $request->macro('user_id', function() {
                return 10024; // 假设这是从 token 中解析出的用户ID
            });
        }

        // 验证通过,或者属于正常的 HTTP 网页请求,直接放行
        return $next($request);
    }

    /**
     * 验证 Token 合法性
     */
    private function checkToken($token): bool
    {
        // 替换为你的真实鉴权逻辑(如 JWT/Redis 验证)
        return $token === 'my_secret_token';
    }
}

请谨慎使用此类代码。

3. 注册中间件

为了让中间件生效,你需要将其注册到全局或者特定的 WebSocket 路由中。

方式 A:注册为全局中间件(最简单)
打开 app/middleware.php(如果没有则创建),将中间件加入数组:

php

return [
    // ... 其他中间件 ...
    \app\middleware\WsAuth::class,
];

请谨慎使用此类代码。

方式 B:通过 config/swoole.php 直接绑定 request 事件(原生 Swoole 风格)
如果你不想过早进入 ThinkPHP 的中间件管线,也可以在 config/swoole.php 中监听底层的 swoole.request(注意需要框架支持或手动在自定义服务中绑定),但通常上面方式 A 的框架级中间件最不易出错且支持标准的响应返回。


💡 如何在后续事件中获取当前连接的用户?

使用 request 事件拦截握手最大的好处是:不需要自己写 Sec-WebSocket-Accept 的加密应答代码,底层会自动处理。

当握手成功后,你可以在 connect 事件(连接成功后触发)中拿到刚刚通过验证的连接。结合 think-swoole的绑定机制,你可以将当前连接的进程号(FD)和用户绑定:

打开 app\listener\WebsocketConnect.php:

php

namespace app\listener;

use think\swoole\Websocket;
use think\facade\Cache;

class WebsocketConnect
{
    public function handle(Websocket $websocket)
    {
        $fd = $websocket->getSender();
        
        // 注意:因为 Swoole 是常驻内存的,你可以在握手成功后
        // 将当前 $fd 对应的用户信息记录到 Redis 或系统的缓存中
        // 这样在接下来的 WebsocketMessage 事件中,就能根据 $fd 知道是谁在发消息
        
        // 示例:将当前连接和对应的用户 ID 绑定,设置有效期
        // Cache::set("ws_fd_{$fd}", $userId, 3600);
    }
}

请谨慎使用此类代码。

技术
许可协议:  CC BY 4.0
分享

相关文章

7月 26, 2026

完美地解决 TP3 老系统数据的平滑读取

为了彻底、完美地解决 TP3 老系统数据的平滑读取,必须解决两个核心痛点: TP3 的盲猜反序列化机制(标量存原样,数组/对象存原生 serialize,同时兼容 json)。 TP3 与 TP6 的缓存前缀(Prefix)不一致。 以下是为你量身定制的终极落地解决方案,通

7月 26, 2026

thinkphp3 redis序列化和反序列化

在老系统重构迁移至 ThinkPHP 6 (TP6) 的过程中,ThinkPHP 3 (TP3) 的 Redis 序列化历史遗留问题是最核心的连环坑之一。 TP3 的 S('key', $value) 缓存方法和底层 Redis 驱动,在存储非字符串(如数组、对象)时,有一套独特的序列化行为。如果不

7月 24, 2026

Table 空间极易发生哈希冲突并溢出

既然你没有在业务中主动调用 Room,只使用了全局广播,那么这个问题就非常明确了:即使你不用房间功能,think-swoole 只要开启了 WebSocket,底层就会强行初始化并注入一个名为 Room 的驱动组件。 [1] 框架底层默认使用 table 类型来作为 Room 的存储介质。当进行全局

下一篇

在原生 WebSocket 模式下,增加握手验证逻辑

上一篇

自己编写一个自定义的 MCP 服务器

最近更新

  • 完美地解决 TP3 老系统数据的平滑读取
  • thinkphp3 redis序列化和反序列化
  • Table 空间极易发生哈希冲突并溢出
  • 将监控程序直接跑在云端
  • AI 驱动型 Facebook 群组关键词监控 Chrome 浏览器插件

热门标签

API CodeGeex Gitkraken Management Manticore Premiere Sublime Swoole ThinkPHP ThinkPHP5

目录

©2026 mdo. 保留部分权利。