基于 Laravel + Swoole + Vue 搭建实时在线聊天室(十七):Websocket 通信用户认证逻辑优化

auth-over-websocket

实现方案

之前我们在 Websocket 通信中,用户认证实现非常粗暴,就是每次从请求字段获取 api_token,然后在服务端判断对应用户记录是否存在:

if (!empty($data['api_token']) && ($user = User::where('api_token', $data['api_token'])->first())) {
    ...
}

我们可以这样来优化这个认证流程:

  1. 在升级到 Websocket 通信的 HTTP 路由中应用 Laravel 认证中间件,确保用户登录后才能建立 Websocket 连接;
  2. 然后在建立 Websocket 连接时,通过 $websocket->loginUsing() 方法设置此连接上的用户 ID,绑定用户认证状态;
  3. 在具体发送消息的 Websocket 中,通过 $websocket->getUserId() 方法获取此连接上的认证用户 ID;
  4. 最后,可以在断开 Websocket 连接时,通过 $websocket->logout() 方法取消用户认证状态绑定。

显然,这里我们结合了 hhxsv5/laravel-sswooletw/laravel-swoole 两个扩展包的实现逻辑来优化 Websocket 通信中用户认证的自动化。

接下来我们来看看具体怎么做。

代码调整

首先让 socket.io 相关路由经过 Laravel 认证中间件 auth:api 过滤,非认证用户不能访问,这可以在 SocketIOController 控制器的构造函数中实现:

public function __construct()
{
    $this->middleware('auth:api');
}

然后调整对应中间件代码(位于 app/Http/Middleware/Authenticate.php),对未认证用户抛出认证异常警告,而不是跳转到 login 路由:

protected function redirectTo($request)
{
    if (! $request->expectsJson()) {
        throw new AuthorizationException("登录后才能建立 Websocket 连接");
    }
}

接下来,我们来到前端,Socket.io 客户端建立连接时需要指定 api_token 请求字段,以便服务端可以读取并进行自动认证,修改 socket.js 代码如下:

...
import store from "./store";
let api_token = store.state.userInfo.token;
const socket = io('http://webchats.test?api_token=' + api_token);
...

最后,打开 routes/websocket.php,调整用户认证相关代码如下:

WebsocketProxy::on('connect', function (WebSocket $websocket, Request $request) {
    // 发送欢迎信息
    $websocket->setSender($request->fd);
    // 建立连接时绑定认证用户信息
    $websocket->loginUsing(auth('api')->user());
});

WebsocketProxy::on('room', function (WebSocket $websocket, $data) {
    if ($userId = $websocket->getUserId()) {
        $user = User::find($userId);
        ...
    } else {
        $websocket->emit('login', '登录后才能进入聊天室');
    }
});

WebsocketProxy::on('message', function (WebSocket $websocket, $data) {
    if ($userId = $websocket->getUserId()) {
        $user = User::find($userId);
        ...
    } else {
        $websocket->emit('login', '登录后才能进入聊天室');
    }
});

...

WebsocketProxy::on('disconnect', function (WebSocket $websocket, $data) {
    roomout($websocket, $data);
    $websocket->logout();
});

...

这里由于 Websocket 默认只提供了 getUserId 方法,没有提供 getUser 方法,所以还要经过再次查询获取认证用户详细信息,你也可以根据底层 loginUsingUserIdgetUserId 的实现自行编写 loginUsingUsergetUser 的实现逻辑,这里就不再详细展开,另外,由于用户认证之后才能建立 Websocket 连接,所以 websocket.phplogin 实现似乎也需要进行重构,感兴趣的同学可以自行去思考并实现,学院君这里就当是抛砖引玉,提供一下大体的思路和方案,更多细节留待你们自己去探究。

小结

关于 Swoole 入门到实战就简单介绍到这里,接下来,学院君将会继续 Laravel 从入门到精通教程的编写,以及把更多精力放到 Golang 的使用和推广上,PHP fpm 框架当然首推 Laravel,至于后续规模上来要进行微服务重构,推荐使用 Golang(Java 党可以绕过)。

上一篇: 基于 Laravel + Swoole + Vue 搭建实时在线聊天室(十六):轮询保持长连接优化

下一篇: 没有下一篇了