Swoole WebSocket认证与授权

Swoole WebSocket:认证授权,保卫你的数据城堡!🛡️

各位观众老爷们,大家好!今天咱们来聊聊Swoole WebSocket 的安全问题,也就是认证与授权。想象一下,你辛辛苦苦搭建了一个在线聊天室,结果谁都能进来插科打诨,甚至有人恶意刷屏、散布谣言,那还得了?你的用户体验岂不是要跌到谷底?😱

所以,认证与授权,就像是给你的 WebSocket 服务建造一道坚固的城墙,只有经过验证的身份才能进入,并且根据身份分配不同的权限,确保你的数据城堡安全可靠!🏰

今天这堂课,咱们就来一起学习如何用 Swoole 为你的 WebSocket 服务加上这道安全锁!

一、啥是认证?啥又是授权?傻傻分不清楚? 🤷‍♂️

很多初学者容易把认证 (Authentication) 和授权 (Authorization) 搞混。其实,它们俩就像是一对好基友,但分工不同:

  • 认证 (Authentication): 确认你是谁!就像你去银行取钱,需要刷身份证、输密码一样,证明你是你,而不是冒名顶替的坏人。
  • 授权 (Authorization): 确认你能干啥!就像银行根据你的账户等级,决定你每天可以取多少钱,可以办理哪些业务一样,确定你是否有权限访问某些资源或执行某些操作。

简单来说:

  • 认证回答 "你是谁?"
  • 授权回答 "你能干什么?"

表格:认证与授权的区别

特性 认证 (Authentication) 授权 (Authorization)
目的 验证用户身份 确定用户权限
回答的问题 你是谁? 你能做什么?
常见方式 用户名密码、Token、OAuth RBAC、ACL
例子 登录网站、刷身份证 访问特定页面、修改数据

二、Swoole WebSocket 认证的几种姿势 💃🕺

Swoole WebSocket 认证的方式有很多,就像武林高手练功,招式各异,但殊途同归。常见的几种方式包括:

  1. 基于 URL 参数的认证:

    这是最简单粗暴的方式,直接在 WebSocket 连接的 URL 中携带认证信息,例如:

    ws://example.com/ws?token=YOUR_TOKEN

    这种方式简单易懂,但安全性较低,因为 URL 中的 token 容易被抓包窃取。

  2. 基于 HTTP Header 的认证:

    将认证信息放在 HTTP Header 中,例如:

    Sec-WebSocket-Protocol: Bearer YOUR_TOKEN

    这种方式比 URL 参数更安全,因为 HTTP Header 不容易被直接看到。

  3. 基于 Cookie 的认证:

    如果你的 WebSocket 服务和 Web 应用共享同一个域名,可以使用 Cookie 来传递认证信息。

  4. 基于自定义协议的认证:

    在建立 WebSocket 连接后,客户端和服务端通过自定义的协议来交换认证信息。这种方式灵活性最高,但实现起来也更复杂。

下面,我们以基于 HTTP Header 的认证为例,来演示如何在 Swoole 中实现认证:

<?php

use SwooleWebSocketServer;
use SwooleHttpRequest;
use SwooleWebSocketFrame;

$server = new Server("0.0.0.0", 9501);

$users = []; // 存储已认证的用户信息

$server->on("open", function (Server $server, Request $request) use (&$users) {
    echo "connection open: {$request->fd}n";

    // 获取 HTTP Header 中的 Token
    $token = $request->header['sec-websocket-protocol'] ?? null;

    if (!$token) {
        echo "Missing token!n";
        $server->disconnect($request->fd); // 断开连接
        return;
    }

    // 验证 Token 的有效性 (这里只是一个示例,实际项目中需要更严谨的验证)
    if ($token === 'YOUR_SECRET_TOKEN') {
        $user_id = uniqid(); // 生成一个用户ID
        $users[$request->fd] = [
            'user_id' => $user_id,
            'username' => 'test_user', // 假设 token 对应一个用户
        ];
        echo "User {$user_id} authenticated!n";
        $server->push($request->fd, "Welcome, test_user!");
    } else {
        echo "Invalid token!n";
        $server->disconnect($request->fd); // 断开连接
    }
});

$server->on("message", function (Server $server, Frame $frame) use (&$users) {
    echo "received message: {$frame->data} from {$frame->fd}n";

    // 检查用户是否已认证
    if (!isset($users[$frame->fd])) {
        $server->push($frame->fd, "Please authenticate first!");
        return;
    }

    $user = $users[$frame->fd];
    $message = "{$user['username']}: {$frame->data}";
    foreach ($server->connections as $fd) {
        $server->push($fd, $message);
    }
});

$server->on("close", function (Server $server, int $fd) use (&$users) {
    echo "connection close: {$fd}n";
    unset($users[$fd]); // 移除用户信息
});

$server->start();

代码解析:

  1. $server->on("open", ...) 在 WebSocket 连接建立时触发。
  2. $request->header['sec-websocket-protocol'] ?? null; 获取 HTTP Header 中的 Sec-WebSocket-Protocol 字段,这就是我们用来传递 Token 的地方。
  3. if (!$token) 检查 Token 是否存在,如果不存在,直接断开连接。
  4. if ($token === 'YOUR_SECRET_TOKEN') 验证 Token 的有效性。这里只是一个简单的示例,实际项目中需要更严谨的验证,例如从数据库中查询 Token 是否有效,是否过期等等。
  5. $users[$request->fd] = [...] 将认证成功的用户信息存储在一个数组中,方便后续使用。
  6. $server->on("message", ...) 在收到客户端消息时触发。
  7. if (!isset($users[$frame->fd])) 检查用户是否已认证,如果未认证,则拒绝处理消息。
  8. $server->on("close", ...) 在 WebSocket 连接关闭时触发,移除用户信息。

客户端代码 (JavaScript):

let ws = new WebSocket("ws://localhost:9501", "YOUR_SECRET_TOKEN");

ws.onopen = () => {
    console.log("Connected!");
    ws.send("Hello, Server!");
};

ws.onmessage = (event) => {
    console.log("Received: " + event.data);
};

ws.onclose = () => {
    console.log("Disconnected!");
};

注意: YOUR_SECRET_TOKEN 只是一个示例,实际项目中一定要使用更复杂、更安全的 Token 生成方式。

三、Swoole WebSocket 授权,让权限飞起来! 🦅

认证解决了 "你是谁" 的问题,接下来就要解决 "你能干什么" 的问题了,也就是授权。 授权的方式有很多,常见的包括:

  1. 基于角色的访问控制 (RBAC): 将用户分配到不同的角色,每个角色拥有不同的权限。
  2. 访问控制列表 (ACL): 为每个资源定义一个访问控制列表,指定哪些用户或角色可以访问该资源。

我们以基于角色的访问控制 (RBAC) 为例,来演示如何在 Swoole 中实现授权:

<?php

use SwooleWebSocketServer;
use SwooleHttpRequest;
use SwooleWebSocketFrame;

$server = new Server("0.0.0.0", 9501);

$users = []; // 存储已认证的用户信息

// 角色和权限的定义
$roles = [
    'admin' => ['send_message', 'delete_message'],
    'user' => ['send_message'],
    'guest' => [], // 游客没有任何权限
];

// 用户和角色的关系
$user_roles = [
    'user1' => 'admin',
    'user2' => 'user',
];

// 权限验证函数
function hasPermission(string $user_id, string $permission) use ($user_roles, $roles): bool
{
    global $user_roles, $roles;

    // 获取用户的角色
    $role = $user_roles[$user_id] ?? 'guest'; // 默认是游客

    // 检查角色是否拥有该权限
    return in_array($permission, $roles[$role] ?? []);
}

$server->on("open", function (Server $server, Request $request) use (&$users, $user_roles, $roles) {
    echo "connection open: {$request->fd}n";

    // 获取 HTTP Header 中的 Token
    $token = $request->header['sec-websocket-protocol'] ?? null;

    if (!$token) {
        echo "Missing token!n";
        $server->disconnect($request->fd); // 断开连接
        return;
    }

    // 验证 Token 的有效性 (这里只是一个示例,实际项目中需要更严谨的验证)
    // 假设 token 对应 user1 或 user2
    $user_id = null;
    if ($token === 'TOKEN_USER1') {
        $user_id = 'user1';
    } elseif ($token === 'TOKEN_USER2') {
        $user_id = 'user2';
    } else {
        echo "Invalid token!n";
        $server->disconnect($request->fd); // 断开连接
        return;
    }

    $users[$request->fd] = [
        'user_id' => $user_id,
        'username' => $user_id, // 假设 user_id 就是 username
    ];
    echo "User {$user_id} authenticated!n";
    $server->push($request->fd, "Welcome, {$user_id}!");

});

$server->on("message", function (Server $server, Frame $frame) use (&$users, $user_roles, $roles) {
    echo "received message: {$frame->data} from {$frame->fd}n";

    // 检查用户是否已认证
    if (!isset($users[$frame->fd])) {
        $server->push($frame->fd, "Please authenticate first!");
        return;
    }

    $user = $users[$frame->fd];
    $user_id = $user['user_id'];

    // 检查用户是否有发送消息的权限
    if (!hasPermission($user_id, 'send_message')) {
        $server->push($frame->fd, "You do not have permission to send messages!");
        return;
    }

    $message = "{$user['username']}: {$frame->data}";
    foreach ($server->connections as $fd) {
        $server->push($fd, $message);
    }
});

$server->on("close", function (Server $server, int $fd) use (&$users) {
    echo "connection close: {$fd}n";
    unset($users[$fd]); // 移除用户信息
});

$server->start();

代码解析:

  1. $roles 定义了角色和权限的关系。例如,admin 角色拥有 send_messagedelete_message 两个权限。
  2. $user_roles 定义了用户和角色的关系。例如,user1 角色是 admin
  3. hasPermission(string $user_id, string $permission) 权限验证函数,用于检查用户是否拥有某个权限。
  4. $server->on("message", ...) 在收到客户端消息时,先检查用户是否已认证,然后调用 hasPermission() 函数检查用户是否有发送消息的权限。

客户端代码 (JavaScript):

// 用户1 (admin)
let ws1 = new WebSocket("ws://localhost:9501", "TOKEN_USER1");

ws1.onopen = () => {
    console.log("User1 (admin) Connected!");
    ws1.send("Hello, Server! I am admin.");
};

ws1.onmessage = (event) => {
    console.log("User1 Received: " + event.data);
};

ws1.onclose = () => {
    console.log("User1 Disconnected!");
};

// 用户2 (user)
let ws2 = new WebSocket("ws://localhost:9501", "TOKEN_USER2");

ws2.onopen = () => {
    console.log("User2 (user) Connected!");
    ws2.send("Hello, Server! I am a regular user.");
};

ws2.onmessage = (event) => {
    console.log("User2 Received: " + event.data);
};

ws2.onclose = () => {
    console.log("User2 Disconnected!");
};

四、安全小贴士 💡

  • 永远不要在客户端存储敏感信息: 例如密码、API 密钥等等。
  • 使用 HTTPS: HTTPS 可以加密 WebSocket 连接,防止数据被窃取。
  • 定期更新 Token: Token 应该有一个有效期,并且定期更新,以防止被长期滥用。
  • 使用安全的随机数生成器: 生成 Token 时,一定要使用安全的随机数生成器,避免生成可预测的 Token。
  • 对用户输入进行验证: 防止恶意用户通过输入特殊字符来攻击你的服务。
  • 记录日志: 记录用户的登录和操作行为,方便排查问题和进行安全审计。

五、总结 📝

认证与授权是保障 WebSocket 服务安全的重要手段。通过认证,我们可以确认用户的身份;通过授权,我们可以控制用户可以访问哪些资源和执行哪些操作。 Swoole 提供了灵活的方式来实现认证与授权,你可以根据自己的需求选择合适的方式。

记住,安全无小事,时刻保持警惕,才能让你的数据城堡固若金汤! 💪

希望今天的课程对大家有所帮助! 咱们下期再见! 👋

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注