Skip to content

RabbitMQ 内置用户认证

概述

内置用户认证是 RabbitMQ 默认提供的认证方式,使用 Mnesia 数据库存储用户凭证。它简单易用,适合小型项目和开发测试环境。本文档详细介绍内置用户认证的配置、管理和最佳实践。

核心知识点

用户数据存储

RabbitMQ 内置用户存储在 Mnesia 数据库中:

┌─────────────────────────────────────────┐
│              Mnesia 数据库               │
├─────────────────────────────────────────┤
│  用户表 (rabbit_user)                   │
│  ├── username (用户名)                   │
│  ├── password_hash (密码哈希)            │
│  ├── hashing_algorithm (哈希算法)        │
│  └── tags (用户标签)                     │
├─────────────────────────────────────────┤
│  权限表 (rabbit_user_permission)        │
│  ├── user (用户)                        │
│  ├── vhost (虚拟主机)                    │
│  ├── configure (配置权限)                │
│  ├── write (写权限)                      │
│  └── read (读权限)                       │
└─────────────────────────────────────────┘

用户标签类型

标签说明权限范围
administrator管理员所有操作,包括用户和策略管理
monitoring监控者查看所有配置和监控数据
policymaker策略制定者管理策略和参数
management管理者使用管理插件
none普通用户仅消息操作

密码哈希算法

RabbitMQ 支持多种密码哈希算法:

┌─────────────────┬──────────────────────────────────┐
│ 算法            │ 说明                              │
├─────────────────┼──────────────────────────────────┤
│ rabbit_password_hashing_sha256  │ 默认,SHA-256   │
│ rabbit_password_hashing_sha512  │ SHA-512,更安全 │
│ rabbit_password_hashing_md5     │ 已废弃,不推荐  │
└─────────────────┴──────────────────────────────────┘

配置示例

基础配置

rabbitmq.conf 中配置密码哈希算法:

conf
# 设置密码哈希算法
password_hashing_module = rabbit_password_hashing_sha256

# 或者使用 SHA-512(更安全)
password_hashing_module = rabbit_password_hashing_sha512

用户管理命令

bash
# 查看所有用户
rabbitmqctl list_users

# 添加用户
rabbitmqctl add_user username password

# 删除用户
rabbitmqctl delete_user username

# 修改密码
rabbitmqctl change_password username new_password

# 清除密码(禁止登录)
rabbitmqctl clear_password username

# 设置用户标签
rabbitmqctl set_user_tags username administrator

# 设置多个标签
rabbitmqctl set_user_tags username administrator monitoring

# 查看用户权限
rabbitmqctl list_user_permissions username

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

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

高级配置

conf
# rabbitmq.conf

# 默认用户(仅用于初始化,生产环境应删除)
# default_user = guest
# default_pass = guest

# 禁止默认用户远程访问
loopback_users = guest

# 或允许多个用户仅本地访问
loopback_users.1 = guest
loopback_users.2 = admin_local

PHP 代码示例

用户管理类

php
<?php

class RabbitMQUserManager
{
    private $rabbitmqHost;
    private $rabbitmqPort;
    private $adminUser;
    private $adminPassword;

    public function __construct(
        string $host = 'localhost',
        int $port = 15672,
        string $adminUser = 'admin',
        string $adminPassword = 'admin'
    ) {
        $this->rabbitmqHost = $host;
        $this->rabbitmqPort = $port;
        $this->adminUser = $adminUser;
        $this->adminPassword = $adminPassword;
    }

    public function createUser(string $username, string $password, array $tags = []): bool
    {
        $url = "http://{$this->rabbitmqHost}:{$this->rabbitmqPort}/api/users/{$username}";
        
        $data = [
            'password' => $password,
            'tags' => implode(',', $tags) ?: ''
        ];

        $response = $this->httpRequest($url, 'PUT', $data);
        
        return $response['status'] === 204;
    }

    public function deleteUser(string $username): bool
    {
        $url = "http://{$this->rabbitmqHost}:{$this->rabbitmqPort}/api/users/{$username}";
        
        $response = $this->httpRequest($url, 'DELETE');
        
        return $response['status'] === 204;
    }

    public function changePassword(string $username, string $newPassword): bool
    {
        $url = "http://{$this->rabbitmqHost}:{$this->rabbitmqPort}/api/users/{$username}";
        
        $data = [
            'password' => $newPassword
        ];

        $response = $this->httpRequest($url, 'PUT', $data);
        
        return $response['status'] === 204;
    }

    public function setPermissions(
        string $username,
        string $vhost = '/',
        string $configure = '.*',
        string $write = '.*',
        string $read = '.*'
    ): bool {
        $vhostEncoded = urlencode($vhost);
        $url = "http://{$this->rabbitmqHost}:{$this->rabbitmqPort}/api/permissions/{$vhostEncoded}/{$username}";
        
        $data = [
            'configure' => $configure,
            'write' => $write,
            'read' => $read
        ];

        $response = $this->httpRequest($url, 'PUT', $data);
        
        return $response['status'] === 204;
    }

    public function listUsers(): array
    {
        $url = "http://{$this->rabbitmqHost}:{$this->rabbitmqPort}/api/users";
        
        $response = $this->httpRequest($url, 'GET');
        
        return json_decode($response['body'], true) ?: [];
    }

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

    private function httpRequest(string $url, string $method, array $data = null): array
    {
        $ch = curl_init($url);
        
        $options = [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_USERPWD => $this->adminUser . ':' . $this->adminPassword,
            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 RabbitMQUserManager('localhost', 15672, 'admin', 'admin_password');

// 创建用户
$manager->createUser('app_user', 'secure_password_123', ['management']);

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

// 查看用户列表
$users = $manager->listUsers();
print_r($users);

安全连接工厂

php
<?php

use PhpAmqpLib\Connection\AMQPStreamConnection;

class SecureConnectionFactory
{
    private $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function createConnection(string $username, string $password): AMQPStreamConnection
    {
        $this->validateCredentials($username, $password);

        return new AMQPStreamConnection(
            $this->config['host'],
            $this->config['port'],
            $username,
            $password,
            $this->config['vhost'] ?? '/',
            false,
            'AMQPLAIN',
            null,
            'en_US',
            $this->config['connection_timeout'] ?? 3.0,
            $this->config['read_write_timeout'] ?? 3.0
        );
    }

    private function validateCredentials(string $username, string $password): void
    {
        if (strlen($username) < 3) {
            throw new InvalidArgumentException('用户名长度不能少于 3 个字符');
        }

        if (strlen($password) < 8) {
            throw new InvalidArgumentException('密码长度不能少于 8 个字符');
        }

        // 检查是否使用默认账户
        if ($username === 'guest' && $this->config['production_mode'] ?? false) {
            throw new RuntimeException('生产环境禁止使用 guest 账户');
        }
    }

    public static function fromEnvironment(): self
    {
        return new self([
            'host' => getenv('RABBITMQ_HOST') ?: 'localhost',
            'port' => (int)(getenv('RABBITMQ_PORT') ?: 5672),
            'vhost' => getenv('RABBITMQ_VHOST') ?: '/',
            'production_mode' => getenv('APP_ENV') === 'production'
        ]);
    }
}

// 使用示例
$factory = SecureConnectionFactory::fromEnvironment();

try {
    $connection = $factory->createConnection(
        getenv('RABBITMQ_USER'),
        getenv('RABBITMQ_PASSWORD')
    );
    echo "连接成功\n";
} catch (Exception $e) {
    echo "连接失败: " . $e->getMessage() . "\n";
}

实际应用场景

场景一:应用级用户管理

php
<?php

class ApplicationUserService
{
    private $userManager;
    private $appPrefix;

    public function __construct(RabbitMQUserManager $userManager, string $appPrefix = 'app_')
    {
        $this->userManager = $userManager;
        $this->appPrefix = $appPrefix;
    }

    public function registerApplication(string $appId, string $vhost = '/'): array
    {
        $username = $this->appPrefix . $appId;
        $password = $this->generateSecurePassword();

        // 创建用户
        $created = $this->userManager->createUser($username, $password, ['']);

        if (!$created) {
            throw new RuntimeException("创建应用用户失败: {$appId}");
        }

        // 设置权限(仅允许访问自己前缀的资源)
        $pattern = "^{$this->appPrefix}{$appId}_.*";
        $this->userManager->setPermissions(
            $username,
            $vhost,
            $pattern,
            $pattern,
            $pattern
        );

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

    public function rotateApplicationPassword(string $appId): string
    {
        $username = $this->appPrefix . $appId;
        $newPassword = $this->generateSecurePassword();

        $changed = $this->userManager->changePassword($username, $newPassword);

        if (!$changed) {
            throw new RuntimeException("密码轮换失败: {$appId}");
        }

        return $newPassword;
    }

    public function revokeApplication(string $appId): bool
    {
        $username = $this->appPrefix . $appId;
        return $this->userManager->deleteUser($username);
    }

    private function generateSecurePassword(int $length = 24): string
    {
        $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
        $password = '';
        
        for ($i = 0; $i < $length; $i++) {
            $password .= $chars[random_int(0, strlen($chars) - 1)];
        }
        
        return $password;
    }
}

场景二:多环境用户隔离

php
<?php

class EnvironmentUserManager
{
    private $userManager;
    private $environments = ['dev', 'staging', 'production'];

    public function setupEnvironmentUsers(string $projectName): void
    {
        foreach ($this->environments as $env) {
            $username = "{$projectName}_{$env}";
            $vhost = "/{$projectName}_{$env}";

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

            // 创建用户
            $password = bin2hex(random_bytes(16));
            $this->userManager->createUser($username, $password, ['management']);

            // 设置权限
            $this->userManager->setPermissions($username, $vhost, '.*', '.*', '.*');

            // 存储凭证到安全存储
            $this->storeCredentials($username, $password, $env);
        }
    }

    private function createVhost(string $vhost): void
    {
        // 通过 HTTP API 创建 vhost
    }

    private function storeCredentials(string $username, string $password, string $env): void
    {
        // 存储到安全的凭证管理系统
    }
}

常见问题与解决方案

问题 1:guest 用户远程访问被拒绝

错误信息

ACCESS_REFUSED - Login was refused using authentication mechanism PLAIN

解决方案

bash
# 方法一:创建新用户并授权
rabbitmqctl add_user admin your_password
rabbitmqctl set_user_tags admin administrator
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

# 方法二:允许 guest 远程访问(不推荐)
# 在 rabbitmq.conf 中注释掉:
# loopback_users = guest
# 改为:
loopback_users = none

问题 2:用户权限不足

错误信息

ACCESS_REFUSED - access to queue 'my_queue' in vhost '/' refused for user 'myuser'

解决方案

bash
# 检查当前权限
rabbitmqctl list_user_permissions myuser

# 设置完整权限
rabbitmqctl set_permissions -p / myuser ".*" ".*" ".*"

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

问题 3:密码包含特殊字符导致认证失败

解决方案

php
<?php
// 确保密码正确编码
$password = 'p@ssw0rd!#$';

// URL 编码(如果通过 API)
$encodedPassword = urlencode($password);

// 或使用转义(命令行)
$escapedPassword = escapeshellarg($password);

最佳实践建议

1. 用户命名规范

php
<?php

class UserNamingConvention
{
    const PREFIX_APP = 'app_';
    const PREFIX_SERVICE = 'svc_';
    const PREFIX_ADMIN = 'adm_';

    public static function generateAppUsername(string $appName, string $env): string
    {
        return self::PREFIX_APP . strtolower($appName) . '_' . $env;
    }

    public static function generateServiceUsername(string $serviceName): string
    {
        return self::PREFIX_SERVICE . strtolower($serviceName);
    }

    public static function generateAdminUsername(string $department): string
    {
        return self::PREFIX_ADMIN . strtolower($department);
    }
}

2. 密码策略

php
<?php

class PasswordPolicy
{
    const MIN_LENGTH = 12;
    const REQUIRE_UPPERCASE = true;
    const REQUIRE_LOWERCASE = true;
    const REQUIRE_DIGIT = true;
    const REQUIRE_SPECIAL = true;

    public static function validate(string $password): array
    {
        $errors = [];

        if (strlen($password) < self::MIN_LENGTH) {
            $errors[] = '密码长度必须至少 ' . self::MIN_LENGTH . ' 个字符';
        }

        if (self::REQUIRE_UPPERCASE && !preg_match('/[A-Z]/', $password)) {
            $errors[] = '密码必须包含大写字母';
        }

        if (self::REQUIRE_LOWERCASE && !preg_match('/[a-z]/', $password)) {
            $errors[] = '密码必须包含小写字母';
        }

        if (self::REQUIRE_DIGIT && !preg_match('/[0-9]/', $password)) {
            $errors[] = '密码必须包含数字';
        }

        if (self::REQUIRE_SPECIAL && !preg_match('/[!@#$%^&*(),.?":{}|<>]/', $password)) {
            $errors[] = '密码必须包含特殊字符';
        }

        return $errors;
    }

    public static function generate(int $length = 20): string
    {
        $uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $lowercase = 'abcdefghijklmnopqrstuvwxyz';
        $digits = '0123456789';
        $special = '!@#$%^&*';

        $allChars = $uppercase . $lowercase . $digits . $special;
        $password = '';

        // 确保每种字符都有
        $password .= $uppercase[random_int(0, strlen($uppercase) - 1)];
        $password .= $lowercase[random_int(0, strlen($lowercase) - 1)];
        $password .= $digits[random_int(0, strlen($digits) - 1)];
        $password .= $special[random_int(0, strlen($special) - 1)];

        // 填充剩余长度
        for ($i = 4; $i < $length; $i++) {
            $password .= $allChars[random_int(0, strlen($allChars) - 1)];
        }

        // 打乱顺序
        return str_shuffle($password);
    }
}

3. 定期审计

php
<?php

class UserAuditService
{
    private $userManager;

    public function auditUsers(): array
    {
        $users = $this->userManager->listUsers();
        $issues = [];

        foreach ($users as $user) {
            // 检查默认账户
            if ($user['name'] === 'guest') {
                $issues[] = [
                    'user' => 'guest',
                    'issue' => '存在默认 guest 账户',
                    'severity' => 'high'
                ];
            }

            // 检查无标签用户
            if (empty($user['tags'])) {
                $issues[] = [
                    'user' => $user['name'],
                    'issue' => '用户没有设置标签',
                    'severity' => 'low'
                ];
            }

            // 检查管理员账户数量
            if (strpos($user['tags'], 'administrator') !== false) {
                $issues[] = [
                    'user' => $user['name'],
                    'issue' => '管理员账户',
                    'severity' => 'info'
                ];
            }
        }

        return $issues;
    }

    public function generateAuditReport(): string
    {
        $issues = $this->auditUsers();
        
        $report = "RabbitMQ 用户审计报告\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n";
        $report .= str_repeat('=', 50) . "\n\n";

        foreach ($issues as $issue) {
            $report .= "[{$issue['severity']}] {$issue['user']}: {$issue['issue']}\n";
        }

        return $report;
    }
}

安全注意事项

重要警告

  1. 生产环境必须删除 guest 用户或设置强密码并禁用远程访问
  2. 不要在代码中硬编码密码,使用环境变量或密钥管理服务
  3. 最小权限原则:只授予用户必需的权限
  4. 定期审计用户:检查是否有僵尸账户或权限过大的账户
  5. 密码轮换:建议每 90 天更换一次密码

相关链接