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
分享

相关文章

10月 2, 2026

iOS 侧载(Sideloading)工具

AltStore 和 Sideloadly 都是目前最流行的 iOS 侧载(Sideloading)工具。 简单来说,它们的作用就是让你绕过苹果的官方 App Store,直接把第三方的 IPA 安装包(也就是 iOS 的应用安装文件)安装到你的 iPhone 或 iPad 上。 通过这两个工具,你

9月 28, 2026

注册 Chat Participant

将当前服务接入 VS Code 的 AI 聊天窗口是完全可行的。VS Code 在近期的版本中正式推出了 Chat Extensions API(github.copilot.chat),允许第三方扩展将自定义的 LLM 服务或工具注册到 Copilot Chat 面板中。 结合当前 code_ge

9月 23, 2026

php intelephense 设置 if 空格

在 VS Code 中,如果你使用的是 PHP Intelephense 插件,控制 if 关键字后面是否加空格的设置是由其内置的格式化引擎(基于 PSR-12 标准)决定的。 你可以通过修改 VS Code 的 settings.json 来配置这个行为: 1. 开启 if 后面的空格(默认/推荐

下一篇

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

上一篇

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

最近更新

  • 比尔盖茨的智慧:想多赚钱,就每天循环做这3件事
  • iOS 侧载(Sideloading)工具
  • 程序员越想创业,越不要急着动手
  • 注册 Chat Participant
  • 中秋给在外游子的一封信

热门标签

API CodeGeex Coding Cursor DeepSeek Docker Gitkraken Harness Laravel Management

目录

©2026 mdo. 保留部分权利。