Skip to content

RabbitMQ 权限模型

概述

RabbitMQ 的权限模型是基于虚拟主机(vhost)的细粒度访问控制系统。通过权限配置,可以精确控制用户对队列、交换机等资源的访问能力。本文档详细介绍 RabbitMQ 权限模型的工作原理和配置方法。

核心知识点

权限模型架构

┌─────────────────────────────────────────────────────────────┐
│                    RabbitMQ 权限模型                         │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │    User     │────▶│   VHost     │────▶│  Resource   │   │
│  │   (用户)    │     │ (虚拟主机)   │     │  (资源)     │   │
│  └─────────────┘     └─────────────┘     └─────────────┘   │
│         │                   │                   │          │
│         │                   │                   │          │
│         ▼                   ▼                   ▼          │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                   Permission                         │   │
│  │  ┌───────────┬───────────┬───────────┬───────────┐  │   │
│  │  │ Configure │   Write   │   Read    │   VHost   │  │   │
│  │  │  配置权限  │   写权限   │   读权限   │  虚拟主机  │  │   │
│  │  └───────────┴───────────┴───────────┴───────────┘  │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

权限类型详解

权限类型说明适用操作
Configure配置权限声明/删除队列、交换机、绑定
Write写权限发布消息到交换机、绑定队列到交换机
Read读权限消费消息、获取消息、声明队列/交换机(仅查询)

权限检查流程

客户端请求操作


┌─────────────────┐
│ 检查用户认证    │──── 失败 ────▶ 拒绝访问
└────────┬────────┘
         │ 成功

┌─────────────────┐
│ 检查 VHost 访问 │──── 无权限 ──▶ 拒绝访问
└────────┬────────┘
         │ 有权限

┌─────────────────┐
│ 检查资源权限    │──── 无权限 ──▶ 拒绝访问
│ (Configure/     │
│  Write/Read)    │
└────────┬────────┘
         │ 有权限

    允许操作

权限匹配规则

权限使用正则表达式匹配资源名称:

权限模式              匹配资源
─────────────────────────────────────
.*                   所有资源
^order_.*            以 order_ 开头的资源
^queue_.*$           以 queue_ 开头的资源
^(queue|exchange)_.* 以 queue_ 或 exchange_ 开头的资源
^$                   空字符串(默认交换机)

配置示例

命令行权限管理

bash
# 查看所有权限
rabbitmqctl list_permissions

# 查看指定 vhost 的权限
rabbitmqctl list_permissions -p /myvhost

# 查看指定用户的权限
rabbitmqctl list_user_permissions username

# 设置权限
# 格式:set_permissions [-p vhost] user configure write read
rabbitmqctl set_permissions -p /myvhost myuser ".*" ".*" ".*"

# 设置特定前缀权限
rabbitmqctl set_permissions -p /myvhost myuser "^app_.*" "^app_.*" "^app_.*"

# 清除权限
rabbitmqctl clear_permissions -p /myvhost myuser

HTTP API 权限管理

bash
# 获取所有权限
curl -u admin:password http://localhost:15672/api/permissions

# 获取指定 vhost 的权限
curl -u admin:password http://localhost:15672/api/permissions/%2F

# 设置权限
curl -u admin:password -X PUT http://localhost:15672/api/permissions/%2F/myuser \
  -H "Content-Type: application/json" \
  -d '{"configure":".*","write":".*","read":".*"}'

# 删除权限
curl -u admin:password -X DELETE http://localhost:15672/api/permissions/%2F/myuser

常见权限配置场景

bash
# 场景 1:管理员 - 完全权限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

# 场景 2:生产者 - 只能发布到特定交换机
rabbitmqctl set_permissions -p / producer "^$" "^events$" "^$"

# 场景 3:消费者 - 只能消费特定队列
rabbitmqctl set_permissions -p / consumer "^$" "^$" "^orders_.*"

# 场景 4:应用服务 - 只能操作自己前缀的资源
rabbitmqctl set_permissions -p / myapp "^myapp_.*" "^myapp_.*" "^myapp_.*"

# 场景 5:监控服务 - 只读权限
rabbitmqctl set_permissions -p / monitoring "^$" "^$" ".*"

PHP 代码示例

权限管理类

php
<?php

class RabbitMQPermissionManager
{
    private $apiUrl;
    private $credentials;

    public function __construct(string $host, int $port, string $user, string $password)
    {
        $this->apiUrl = "http://{$host}:{$port}/api";
        $this->credentials = [$user, $password];
    }

    public function setPermissions(
        string $username,
        string $vhost,
        string $configure = '.*',
        string $write = '.*',
        string $read = '.*'
    ): bool {
        $vhostEncoded = urlencode($vhost);
        $url = "{$this->apiUrl}/permissions/{$vhostEncoded}/{$username}";

        $response = $this->httpRequest($url, 'PUT', [
            'configure' => $configure,
            'write' => $write,
            'read' => $read
        ]);

        return $response['status'] === 204;
    }

    public function getPermissions(string $vhost = null): array
    {
        if ($vhost) {
            $vhostEncoded = urlencode($vhost);
            $url = "{$this->apiUrl}/permissions/{$vhostEncoded}";
        } else {
            $url = "{$this->apiUrl}/permissions";
        }

        $response = $this->httpRequest($url, 'GET');
        
        return json_decode($response['body'], true) ?: [];
    }

    public function getUserPermissions(string $username): array
    {
        $url = "{$this->apiUrl}/users/{$username}/permissions";
        $response = $this->httpRequest($url, 'GET');
        
        return json_decode($response['body'], true) ?: [];
    }

    public function deletePermissions(string $username, string $vhost): bool
    {
        $vhostEncoded = urlencode($vhost);
        $url = "{$this->apiUrl}/permissions/{$vhostEncoded}/{$username}";
        
        $response = $this->httpRequest($url, 'DELETE');
        
        return $response['status'] === 204;
    }

    public function checkPermission(
        string $username,
        string $vhost,
        string $resource,
        string $name,
        string $permission
    ): bool {
        $userPerms = $this->getUserPermissions($username);
        
        foreach ($userPerms as $perm) {
            if ($perm['vhost'] === $vhost) {
                $pattern = $perm[$permission] ?? '';
                return preg_match('/' . $pattern . '/', $name) === 1;
            }
        }
        
        return false;
    }

    private function httpRequest(string $url, string $method, array $data = null): array
    {
        $ch = curl_init($url);
        
        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_USERPWD => implode(':', $this->credentials),
            CURLOPT_HTTPHEADER => ['Content-Type: application/json']
        ];

        if ($data !== null) {
            $options[CURLOPT_POSTFIELDS] = json_encode($data);
        }

        curl_setopt_array($ch, $options);
        
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return ['status' => $status, 'body' => $body];
    }
}

// 使用示例
$manager = new RabbitMQPermissionManager('localhost', 15672, 'admin', 'password');

// 设置权限
$manager->setPermissions('app_user', '/', '^app_.*', '^app_.*', '^app_.*');

// 查看用户权限
$perms = $manager->getUserPermissions('app_user');
print_r($perms);

// 检查权限
$canRead = $manager->checkPermission('app_user', '/', 'queue', 'app_orders', 'read');
echo "可以读取: " . ($canRead ? '是' : '否') . "\n";

权限模板管理

php
<?php

class PermissionTemplateManager
{
    private $permissionManager;

    private $templates = [
        'admin' => [
            'configure' => '.*',
            'write' => '.*',
            'read' => '.*'
        ],
        'producer' => [
            'configure' => '^$',
            'write' => '.*',
            'read' => '^$'
        ],
        'consumer' => [
            'configure' => '^$',
            'write' => '^$',
            'read' => '.*'
        ],
        'monitoring' => [
            'configure' => '^$',
            'write' => '^$',
            'read' => '.*'
        ],
        'app_isolated' => [
            'configure' => '^{{app_name}}_.*',
            'write' => '^{{app_name}}_.*',
            'read' => '^{{app_name}}_.*'
        ]
    ];

    public function applyTemplate(
        string $username,
        string $vhost,
        string $templateName,
        array $variables = []
    ): bool {
        if (!isset($this->templates[$templateName])) {
            throw new InvalidArgumentException("模板不存在: {$templateName}");
        }

        $template = $this->templates[$templateName];
        
        $permissions = [
            'configure' => $this->resolveTemplate($template['configure'], $variables),
            'write' => $this->resolveTemplate($template['write'], $variables),
            'read' => $this->resolveTemplate($template['read'], $variables)
        ];

        return $this->permissionManager->setPermissions(
            $username,
            $vhost,
            $permissions['configure'],
            $permissions['write'],
            $permissions['read']
        );
    }

    private function resolveTemplate(string $template, array $variables): string
    {
        foreach ($variables as $key => $value) {
            $template = str_replace('{{' . $key . '}}', $value, $template);
        }
        return $template;
    }

    public function createAppUser(string $appName, string $vhost = '/'): array
    {
        $username = 'app_' . $appName;
        $password = bin2hex(random_bytes(16));

        // 创建用户(需要用户管理权限)
        // ...

        // 应用隔离模板
        $this->applyTemplate($username, $vhost, 'app_isolated', [
            'app_name' => $appName
        ]);

        return [
            'username' => $username,
            'password' => $password,
            'vhost' => $vhost
        ];
    }
}

// 使用示例
$templateManager = new PermissionTemplateManager($permissionManager);

// 应用预定义模板
$templateManager->applyTemplate('producer_user', '/', 'producer');
$templateManager->applyTemplate('consumer_user', '/', 'consumer');

// 创建隔离的应用用户
$credentials = $templateManager->createAppUser('orders');
print_r($credentials);

权限审计服务

php
<?php

class PermissionAuditService
{
    private $permissionManager;

    public function auditAllPermissions(): array
    {
        $allPermissions = $this->permissionManager->getPermissions();
        $issues = [];

        foreach ($allPermissions as $perm) {
            $issues = array_merge($issues, $this->auditPermission($perm));
        }

        return $issues;
    }

    private function auditPermission(array $perm): array
    {
        $issues = [];

        // 检查过于宽松的权限
        if ($perm['configure'] === '.*' && $perm['write'] === '.*' && $perm['read'] === '.*') {
            $issues[] = [
                'user' => $perm['user'],
                'vhost' => $perm['vhost'],
                'severity' => 'high',
                'issue' => '用户拥有完全权限,建议限制权限范围'
            ];
        }

        // 检查空权限
        if ($perm['configure'] === '^$' && $perm['write'] === '^$' && $perm['read'] === '^$') {
            $issues[] = [
                'user' => $perm['user'],
                'vhost' => $perm['vhost'],
                'severity' => 'medium',
                'issue' => '用户没有任何权限'
            ];
        }

        // 检查无效的正则表达式
        foreach (['configure', 'write', 'read'] as $type) {
            if (@preg_match('/' . $perm[$type] . '/', '') === false) {
                $issues[] = [
                    'user' => $perm['user'],
                    'vhost' => $perm['vhost'],
                    'severity' => 'high',
                    'issue' => "无效的 {$type} 正则表达式: {$perm[$type]}"
                ];
            }
        }

        return $issues;
    }

    public function generatePermissionReport(): string
    {
        $permissions = $this->permissionManager->getPermissions();
        $issues = $this->auditAllPermissions();

        $report = "# RabbitMQ 权限审计报告\n\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n\n";
        
        $report .= "## 权限概览\n\n";
        $report .= "| 用户 | VHost | Configure | Write | Read |\n";
        $report .= "|------|-------|-----------|-------|------|\n";
        
        foreach ($permissions as $perm) {
            $report .= sprintf(
                "| %s | %s | `%s` | `%s` | `%s` |\n",
                $perm['user'],
                $perm['vhost'],
                $perm['configure'],
                $perm['write'],
                $perm['read']
            );
        }

        if (!empty($issues)) {
            $report .= "\n## 发现的问题\n\n";
            
            foreach ($issues as $issue) {
                $report .= "- **[{$issue['severity']}]** {$issue['user']}@{$issue['vhost']}: {$issue['issue']}\n";
            }
        }

        return $report;
    }

    public function comparePermissions(string $user1, string $user2): array
    {
        $perms1 = $this->permissionManager->getUserPermissions($user1);
        $perms2 = $this->permissionManager->getUserPermissions($user2);

        return [
            'user1' => $user1,
            'user2' => $user2,
            'user1_permissions' => $perms1,
            'user2_permissions' => $perms2,
            'differences' => $this->findDifferences($perms1, $perms2)
        ];
    }

    private function findDifferences(array $perms1, array $perms2): array
    {
        $diff = [];

        foreach ($perms1 as $p1) {
            $vhost = $p1['vhost'];
            $p2 = $this->findPermissionByVhost($perms2, $vhost);

            if (!$p2) {
                $diff[] = [
                    'vhost' => $vhost,
                    'type' => 'missing_in_user2',
                    'details' => $p1
                ];
                continue;
            }

            foreach (['configure', 'write', 'read'] as $type) {
                if ($p1[$type] !== $p2[$type]) {
                    $diff[] = [
                        'vhost' => $vhost,
                        'type' => $type . '_mismatch',
                        'user1' => $p1[$type],
                        'user2' => $p2[$type]
                    ];
                }
            }
        }

        return $diff;
    }

    private function findPermissionByVhost(array $perms, string $vhost): ?array
    {
        foreach ($perms as $perm) {
            if ($perm['vhost'] === $vhost) {
                return $perm;
            }
        }
        return null;
    }
}

实际应用场景

场景一:多租户权限隔离

php
<?php

class MultiTenantPermissionManager
{
    private $permissionManager;

    public function setupTenant(string $tenantId): void
    {
        $vhost = '/tenant_' . $tenantId;
        $username = 'tenant_' . $tenantId;

        // 创建 vhost
        $this->createVhost($vhost);

        // 创建用户
        $password = $this->createUser($username);

        // 设置权限(完全隔离)
        $this->permissionManager->setPermissions(
            $username,
            $vhost,
            '.*',
            '.*',
            '.*'
        );

        return [
            'vhost' => $vhost,
            'username' => $username,
            'password' => $password
        ];
    }

    public function grantCrossTenantAccess(
        string $sourceTenant,
        string $targetTenant,
        string $resourcePattern,
        string $accessType
    ): void {
        $sourceUser = 'tenant_' . $sourceTenant;
        $targetVhost = '/tenant_' . $targetTenant;

        // 获取当前权限
        $currentPerms = $this->permissionManager->getUserPermissions($sourceUser);
        
        // 更新权限以包含跨租户访问
        // ...
    }
}

场景二:动态权限管理

php
<?php

class DynamicPermissionService
{
    public function grantTemporaryAccess(
        string $username,
        string $resource,
        string $permission,
        int $duration
    ): void {
        $expiry = time() + $duration;
        
        // 存储临时权限
        $this->storeTemporaryPermission([
            'username' => $username,
            'resource' => $resource,
            'permission' => $permission,
            'expiry' => $expiry
        ]);

        // 应用权限
        $this->applyTemporaryPermission($username, $resource, $permission);
    }

    public function revokeExpiredPermissions(): void
    {
        $expired = $this->getExpiredPermissions();
        
        foreach ($expired as $perm) {
            $this->revokePermission($perm['username'], $perm['resource']);
            $this->removeTemporaryPermissionRecord($perm['id']);
        }
    }
}

常见问题与解决方案

问题 1:权限不生效

解决方案

bash
# 检查权限是否正确设置
rabbitmqctl list_user_permissions username

# 检查正则表达式是否正确
# 测试正则匹配
php -r "var_dump(preg_match('/^app_.*/', 'app_orders'));"

# 确保用户已认证
rabbitmqctl authenticate_user username password

问题 2:默认交换机权限

解决方案

bash
# 默认交换机名称为空字符串
# 设置对默认交换机的写权限
rabbitmqctl set_permissions -p / myuser "^$" "^$" "^myqueue$"

最佳实践建议

1. 最小权限原则

php
<?php
// 只授予必需的权限
class LeastPrivilegeGrantor
{
    public function grantProducerPermissions(string $username, string $exchangeName): void
    {
        $this->permissionManager->setPermissions(
            $username,
            '/',
            '^$',           // 无配置权限
            "^{$exchangeName}$",  // 只能写指定交换机
            '^$'            // 无读权限
        );
    }

    public function grantConsumerPermissions(string $username, string $queuePattern): void
    {
        $this->permissionManager->setPermissions(
            $username,
            '/',
            '^$',           // 无配置权限
            '^$',           // 无写权限
            "^{$queuePattern}$"  // 只能读匹配的队列
        );
    }
}

2. 权限命名规范

php
<?php
class PermissionNamingConvention
{
    public static function generatePattern(string $app, string $env, string $resource): string
    {
        return "^{$app}_{$env}_{$resource}.*$";
    }

    public static function generateAppPattern(string $app): string
    {
        return "^{$app}_.*$";
    }
}

安全注意事项

安全警告

  1. 避免使用 .* 通配符:尽量使用具体的资源名称模式
  2. 定期审计权限:检查是否有权限过大的账户
  3. 分离职责:生产者和消费者使用不同账户
  4. 监控权限变更:记录所有权限修改操作

相关链接