workerman连接建立后需手动从$http_header['authorization']提取jwt token并剥离bearer前缀,或在tcp直连时由客户端首次消息发送含token字段的json,服务端在onmessage中校验;gatewayworker模式下可结合redis缓存验证。

Workerman连接建立后怎么拿到JWT Token
Workerman本身不处理HTTP请求头或URL参数的自动解析,WebSocket握手阶段的onWebSocketConnect回调里,$http_header只包含原始Header,而Token通常藏在Authorization头或URL查询参数中。必须手动提取,不能依赖框架自动注入。
常见错误是直接读$_GET['token']——这在纯TCP或WebSocket长连接中根本不存在,只有GatewayWorker配合WebServer(如Nginx)反向代理时,才可能通过X-Forwarded-For等头透传,但$_GET始终为空。
- WebSocket场景:从
$http_header['authorization']取值,注意可能带Bearer前缀,需用trim($header, 'Bearer ')剥离 - TCP直连场景:客户端首次发包必须是JSON格式含
token字段,服务端在onMessage里先校验再绑定身份,不能等到后续消息才处理 - GatewayWorker模式:可在
onConnect中解析$connection->getRemoteIp()+$connection->context->session_id查Redis缓存,但前提是登录态已由前置Web服务写入
JWT验证失败时为什么不能直接close()
直接调用$connection->close()看似干净,但在高并发下会触发大量未处理的连接中断,尤其当客户端重连逻辑不完善时,容易形成“连接风暴”。更关键的是,关闭连接不等于释放资源——如果之前已为该连接分配了user_id、permissions等上下文,没清理会导致内存泄漏。
正确做法是:验证失败后,先清空连接上下文(如unset($connection->uid)),再发送拒绝消息(如{"code":401,"msg":"invalid token"}),最后调用$connection->close()。若使用GatewayWorker,还应同步删除Register服务中的注册信息,否则gateway->sendToUid()仍会尝试投递。
权限校验该放在哪一层:连接层还是业务层
权限控制必须分两层做,只放一层必然出问题。
连接层(onWebSocketConnect或onMessage开头)只做基础身份认证:验证Token签名、过期时间、用户是否存在。这一层失败就终止流程,不进入业务逻辑。
业务层(具体消息处理器内)才做细粒度权限检查:比如收到{"action":"delete_user","id":123},要查当前用户permissions数组是否含"user:delete",而不是只认is_admin === true。这里最容易踩的坑是把权限字符串硬编码成"admin",实际应走数据库或Redis查用户角色关联的权限路径列表。
- 不要在
onConnect里查完整权限表——连接建立频次远高于操作频次,会压垮DB - 建议Token payload里只存
uid和role_id,权限列表由业务层按需查缓存(如redis->hGetAll("perms:{$uid}")) - 若用GatewayWorker,
BusinessWorker中onMessage收到消息后,先if (!in_array($action, $connection->permissions)) { return; },别绕过
Redis存储权限数据时key设计要注意什么
用Redis存权限最怕key冲突和过期混乱。比如用"user:{$uid}:perms"作key,看似合理,但当用户权限变更时,旧key不会自动失效,客户端可能还在用过期权限。
必须强制引入版本号或时间戳。推荐方案:"perms:{$uid}:v{$version}",其中$version来自用户权限表的updated_at时间戳(转为秒级整数)。每次权限变更,更新DB的同时,用DEL perms:{$uid}:v*清旧key,再写新key并设TTL(如7200秒)。
另一个坑是用Hash结构存权限却忘了HSCAN不支持模糊匹配——想查"order:*"类通配权限时,必须提前把所有可能的权限项("order:create"、"order:pay")全写进Hash,不能靠运行时正则匹配。











