Skip to content

RabbitMQ LDAP 认证

概述

LDAP(Lightweight Directory Access Protocol)认证是企业环境中常用的集中式身份认证方式。通过 LDAP 集成,RabbitMQ 可以与企业现有的用户目录服务(如 Active Directory、OpenLDAP)对接,实现统一的用户管理和认证。

核心知识点

LDAP 认证架构

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Client    │────▶│  RabbitMQ   │────▶│    LDAP     │
│  (应用程序)  │◀────│   Server    │◀────│   Server    │
└─────────────┘     └─────────────┘     └─────────────┘

                           │ 1. 接收连接请求
                           │ 2. 提取用户凭证
                           │ 3. 查询 LDAP 服务器
                           │ 4. 验证用户身份
                           │ 5. 获取用户属性/组
                           │ 6. 映射权限标签
                           │ 7. 返回认证结果

                    ┌──────┴──────┐
                    │  LDAP 插件  │
                    │  rabbitmq_  │
                    │  auth_ldap  │
                    └─────────────┘

LDAP 认证流程

1. 客户端连接请求
   └── 发送用户名和密码

2. RabbitMQ LDAP 插件处理
   ├── 构建 LDAP 查询 DN
   ├── 连接 LDAP 服务器
   └── 执行绑定操作验证密码

3. 权限标签映射(可选)
   ├── 查询用户所属组
   ├── 根据组映射 RabbitMQ 标签
   └── 应用权限规则

4. 返回认证结果
   └── 建立或拒绝连接

支持的 LDAP 服务器

LDAP 服务器兼容性说明
Active Directory完全支持微软企业目录服务
OpenLDAP完全支持开源 LDAP 实现
389 Directory完全支持Red Hat 目录服务
Apache DS完全支持Apache 目录服务
FreeIPA完全支持Red Hat 身份管理

配置示例

安装 LDAP 插件

bash
# 启用 LDAP 认证插件
rabbitmq-plugins enable rabbitmq_auth_backend_ldap

# 验证插件已启用
rabbitmq-plugins list | grep ldap

基础配置

rabbitmq.conf 中配置 LDAP 连接:

conf
# 启用 LDAP 认证后端
auth_backends.1 = ldap

# LDAP 服务器配置
auth_ldap.servers.1 = ldap.example.com
auth_ldap.servers.2 = ldap-backup.example.com

# LDAP 服务器端口
auth_ldap.port = 389

# 使用 TLS 加密连接
auth_ldap.use_ssl = false
auth_ldap.use_starttls = true

# 连接超时(毫秒)
auth_ldap.timeout = 5000

# 连接池大小
auth_ldap.pool_size = 64

# 日志级别(用于调试)
auth_ldap.log = network

Active Directory 配置

conf
# AD 域控制器
auth_ldap.servers.1 = dc1.example.com
auth_ldap.servers.2 = dc2.example.com

# AD 端口(LDAPS)
auth_ldap.port = 636
auth_ldap.use_ssl = true

# 用户 DN 模板
auth_ldap.user_dn_pattern = ${username}@example.com

# 或使用 userPrincipalName
auth_ldap.user_bind_pattern = ${username}@example.com

# 基础 DN(用于搜索)
auth_ldap.dn_lookup_base = dc=example,dc=com

# 用户搜索过滤器
auth_ldap.dn_lookup_attribute = sAMAccountName
auth_ldap.dn_lookup_filter = (&(objectClass=user)(sAMAccountName=${username}))

# 组搜索配置
auth_ldap.group_lookup_base = ou=Groups,dc=example,dc=com
auth_ldap.group_lookup_filter = (&(objectClass=group)(member=${user_dn}))

OpenLDAP 配置

conf
# OpenLDAP 服务器
auth_ldap.servers.1 = ldap.example.com

# 端口和加密
auth_ldap.port = 389
auth_ldap.use_starttls = true

# 基础 DN
auth_ldap.dn_lookup_base = ou=Users,dc=example,dc=com

# 用户 DN 模式
auth_ldap.user_dn_pattern = uid=${username},ou=Users,dc=example,dc=com

# 用户搜索
auth_ldap.dn_lookup_attribute = uid
auth_ldap.dn_lookup_filter = (&(objectClass=inetOrgPerson)(uid=${username}))

# 组搜索
auth_ldap.group_lookup_base = ou=Groups,dc=example,dc=com
auth_ldap.group_lookup_filter = (&(objectClass=groupOfNames)(member=${user_dn}))

权限标签映射

conf
# 标签映射配置
# 格式:tag = LDAP 组 DN

auth_ldap.tag_queries.administrator = {memberOf, "cn=rabbitmq-admins,ou=Groups,dc=example,dc=com"}
auth_ldap.tag_queries.monitoring = {memberOf, "cn=rabbitmq-monitoring,ou=Groups,dc=example,dc=com"}
auth_ldap.tag_queries.management = {memberOf, "cn=rabbitmq-users,ou=Groups,dc=example,dc=com"}

高级配置

conf
# 绑定 DN(用于搜索用户)
auth_ldap.bind_dn = cn=rabbitmq,ou=Service Accounts,dc=example,dc=com
auth_ldap.bind_password = ${RABBITMQ_LDAP_BIND_PASSWORD}

# 资源权限查询
auth_ldap.vhost_access_query = {constant, true}
auth_ldap.resource_access_query.resource_queue = {permission, "queue", ${name}}
auth_ldap.resource_access_query.resource_exchange = {permission, "exchange", ${name}}

# 自定义属性映射
auth_ldap.attribute_for_group = memberOf
auth_ldap.attribute_for_user_id = uid

PHP 代码示例

LDAP 认证连接

php
<?php

use PhpAmqpLib\Connection\AMQPStreamConnection;

class LdapAuthRabbitMQConnection
{
    private $rabbitmqConfig;
    private $ldapConfig;

    public function __construct(array $rabbitmqConfig, array $ldapConfig)
    {
        $this->rabbitmqConfig = $rabbitmqConfig;
        $this->ldapConfig = $ldapConfig;
    }

    public function connectWithLdapCredentials(string $username, string $password): ?AMQPStreamConnection
    {
        // 1. 先验证 LDAP 凭证
        if (!$this->validateLdapCredentials($username, $password)) {
            throw new RuntimeException('LDAP 认证失败');
        }

        // 2. 使用相同凭证连接 RabbitMQ
        try {
            return new AMQPStreamConnection(
                $this->rabbitmqConfig['host'],
                $this->rabbitmqConfig['port'],
                $username,
                $password,
                $this->rabbitmqConfig['vhost'] ?? '/'
            );
        } catch (Exception $e) {
            throw new RuntimeException('RabbitMQ 连接失败: ' . $e->getMessage());
        }
    }

    private function validateLdapCredentials(string $username, string $password): bool
    {
        $ldapConn = ldap_connect($this->ldapConfig['server'], $this->ldapConfig['port'] ?? 389);
        
        if (!$ldapConn) {
            return false;
        }

        ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);

        // 构建用户 DN
        $userDn = sprintf($this->ldapConfig['user_dn_pattern'], $username);

        // 尝试绑定
        $bind = @ldap_bind($ldapConn, $userDn, $password);

        ldap_close($ldapConn);

        return $bind !== false;
    }

    public function getUserGroups(string $username, string $password): array
    {
        $ldapConn = ldap_connect($this->ldapConfig['server'], $this->ldapConfig['port'] ?? 389);
        
        if (!$ldapConn) {
            return [];
        }

        ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);

        $userDn = sprintf($this->ldapConfig['user_dn_pattern'], $username);
        
        if (!@ldap_bind($ldapConn, $userDn, $password)) {
            ldap_close($ldapConn);
            return [];
        }

        // 搜索用户组
        $filter = sprintf($this->ldapConfig['group_filter'], $userDn);
        $search = ldap_search($ldapConn, $this->ldapConfig['group_base_dn'], $filter, ['cn']);
        
        $entries = ldap_get_entries($ldapConn, $search);
        $groups = [];

        for ($i = 0; $i < $entries['count']; $i++) {
            $groups[] = $entries[$i]['cn'][0];
        }

        ldap_close($ldapConn);

        return $groups;
    }
}

// 使用示例
$connection = new LdapAuthRabbitMQConnection(
    [
        'host' => 'localhost',
        'port' => 5672,
        'vhost' => '/'
    ],
    [
        'server' => 'ldap.example.com',
        'port' => 389,
        'user_dn_pattern' => 'uid=%s,ou=Users,dc=example,dc=com',
        'group_base_dn' => 'ou=Groups,dc=example,dc=com',
        'group_filter' => '(member=%s)'
    ]
);

try {
    $conn = $connection->connectWithLdapCredentials('john.doe', 'password123');
    echo "LDAP 认证连接成功\n";
    
    $groups = $connection->getUserGroups('john.doe', 'password123');
    echo "用户所属组: " . implode(', ', $groups) . "\n";
    
} catch (Exception $e) {
    echo "错误: " . $e->getMessage() . "\n";
}

LDAP 用户同步服务

php
<?php

class LdapUserSyncService
{
    private $ldapConn;
    private $rabbitmqApiUrl;
    private $rabbitmqCredentials;

    public function __construct(
        string $ldapServer,
        array $rabbitmqConfig
    ) {
        $this->ldapConn = ldap_connect($ldapServer);
        $this->rabbitmqApiUrl = "http://{$rabbitmqConfig['host']}:15672/api";
        $this->rabbitmqCredentials = $rabbitmqConfig['credentials'];
    }

    public function syncGroupToRabbitMQ(string $groupDn, string $vhost = '/'): array
    {
        $results = [
            'synced' => 0,
            'failed' => 0,
            'errors' => []
        ];

        // 绑定到 LDAP
        if (!ldap_bind($this->ldapConn, $this->rabbitmqCredentials['bind_dn'], $this->rabbitmqCredentials['bind_password'])) {
            throw new RuntimeException('LDAP 绑定失败');
        }

        // 获取组成员
        $filter = "(memberOf={$groupDn})";
        $search = ldap_search($this->ldapConn, $this->getBaseDn(), $filter, ['uid', 'cn', 'mail']);
        $entries = ldap_get_entries($this->ldapConn, $search);

        for ($i = 0; $i < $entries['count']; $i++) {
            $username = $entries[$i]['uid'][0] ?? null;
            
            if (!$username) {
                continue;
            }

            try {
                $this->ensureRabbitMQUser($username, $vhost);
                $results['synced']++;
            } catch (Exception $e) {
                $results['failed']++;
                $results['errors'][] = [
                    'user' => $username,
                    'error' => $e->getMessage()
                ];
            }
        }

        return $results;
    }

    private function ensureRabbitMQUser(string $username, string $vhost): void
    {
        $url = "{$this->rabbitmqApiUrl}/users/{$username}";
        
        // 创建或更新用户(密码为空,使用 LDAP 认证)
        $this->httpRequest($url, 'PUT', [
            'password' => '',
            'tags' => 'management'
        ]);

        // 设置权限
        $vhostEncoded = urlencode($vhost);
        $permUrl = "{$this->rabbitmqApiUrl}/permissions/{$vhostEncoded}/{$username}";
        
        $this->httpRequest($permUrl, 'PUT', [
            'configure' => '^' . $username . '_.*',
            'write' => '^' . $username . '_.*',
            'read' => '^' . $username . '_.*'
        ]);
    }

    private function httpRequest(string $url, string $method, array $data): array
    {
        $ch = curl_init($url);
        
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_CUSTOMREQUEST => $method,
            CURLOPT_USERPWD => $this->rabbitmqCredentials['api_user'] . ':' . $this->rabbitmqCredentials['api_password'],
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_POSTFIELDS => json_encode($data)
        ]);
        
        $response = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

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

    private function getBaseDn(): string
    {
        return 'dc=example,dc=com';
    }
}

Active Directory 集成

php
<?php

class ActiveDirectoryIntegration
{
    private $adConfig;

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

    public function authenticate(string $username, string $password): array
    {
        $ldapConn = ldap_connect($this->adConfig['server'], $this->adConfig['port'] ?? 389);
        
        if (!$ldapConn) {
            return ['success' => false, 'error' => '无法连接到 AD 服务器'];
        }

        ldap_set_option($ldapConn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($ldapConn, LDAP_OPT_REFERRALS, 0);

        // AD 使用 userPrincipalName 格式
        $userPrincipalName = $username . '@' . $this->adConfig['domain'];
        
        $bind = @ldap_bind($ldapConn, $userPrincipalName, $password);

        if (!$bind) {
            ldap_close($ldapConn);
            return ['success' => false, 'error' => '认证失败'];
        }

        // 获取用户信息
        $baseDn = $this->adConfig['base_dn'];
        $filter = "(sAMAccountName={$username})";
        $attributes = ['displayName', 'mail', 'memberOf', 'department'];
        
        $search = ldap_search($ldapConn, $baseDn, $filter, $attributes);
        $entries = ldap_get_entries($ldapConn, $search);

        $userInfo = [
            'username' => $username,
            'display_name' => $entries[0]['displayname'][0] ?? $username,
            'email' => $entries[0]['mail'][0] ?? null,
            'department' => $entries[0]['department'][0] ?? null,
            'groups' => []
        ];

        // 提取组信息
        if (isset($entries[0]['memberof'])) {
            for ($i = 0; $i < $entries[0]['memberof']['count']; $i++) {
                $groupDn = $entries[0]['memberof'][$i];
                if (preg_match('/^CN=([^,]+)/', $groupDn, $matches)) {
                    $userInfo['groups'][] = $matches[1];
                }
            }
        }

        ldap_close($ldapConn);

        return [
            'success' => true,
            'user' => $userInfo
        ];
    }

    public function mapGroupsToRabbitMQTags(array $groups): array
    {
        $tagMapping = $this->adConfig['tag_mapping'] ?? [];
        $tags = [];

        foreach ($groups as $group) {
            if (isset($tagMapping[$group])) {
                $tags[] = $tagMapping[$group];
            }
        }

        return array_unique($tags);
    }
}

// 使用示例
$ad = new ActiveDirectoryIntegration([
    'server' => 'ldap://dc.example.com',
    'port' => 389,
    'domain' => 'example.com',
    'base_dn' => 'dc=example,dc=com',
    'tag_mapping' => [
        'RabbitMQ Admins' => 'administrator',
        'RabbitMQ Monitoring' => 'monitoring',
        'RabbitMQ Users' => 'management'
    ]
]);

$result = $ad->authenticate('john.doe', 'password');

if ($result['success']) {
    $tags = $ad->mapGroupsToRabbitMQTags($result['user']['groups']);
    echo "用户: {$result['user']['display_name']}\n";
    echo "RabbitMQ 标签: " . implode(', ', $tags) . "\n";
}

实际应用场景

场景一:企业单点登录

php
<?php

class EnterpriseSSO
{
    private $ldapAuth;
    private $rabbitmqFactory;

    public function processLogin(string $username, string $password): array
    {
        // 1. LDAP 认证
        $ldapResult = $this->ldapAuth->authenticate($username, $password);
        
        if (!$ldapResult['success']) {
            return [
                'success' => false,
                'error' => '认证失败'
            ];
        }

        // 2. 获取 RabbitMQ 标签
        $tags = $this->ldapAuth->mapGroupsToRabbitMQTags($ldapResult['user']['groups']);

        // 3. 确保用户在 RabbitMQ 中存在
        $this->ensureRabbitMQUser($username, $tags);

        // 4. 生成会话令牌
        $token = $this->generateSessionToken($username);

        return [
            'success' => true,
            'token' => $token,
            'user' => $ldapResult['user'],
            'rabbitmq_tags' => $tags
        ];
    }

    private function ensureRabbitMQUser(string $username, array $tags): void
    {
        // 通过 API 创建/更新用户
    }

    private function generateSessionToken(string $username): string
    {
        return bin2hex(random_bytes(32));
    }
}

场景二:动态权限管理

php
<?php

class DynamicPermissionManager
{
    private $ldapConn;
    private $rabbitmqApi;

    public function syncPermissions(): void
    {
        // 获取所有 RabbitMQ 用户
        $users = $this->rabbitmqApi->listUsers();

        foreach ($users as $user) {
            // 从 LDAP 获取最新组信息
            $groups = $this->getUserGroupsFromLdap($user['name']);
            
            // 计算新的权限
            $permissions = $this->calculatePermissions($groups);
            
            // 更新权限
            $this->updateRabbitMQPermissions($user['name'], $permissions);
        }
    }

    private function getUserGroupsFromLdap(string $username): array
    {
        // LDAP 查询用户组
        return [];
    }

    private function calculatePermissions(array $groups): array
    {
        // 根据组计算权限
        return [
            'configure' => '.*',
            'write' => '.*',
            'read' => '.*'
        ];
    }

    private function updateRabbitMQPermissions(string $username, array $permissions): void
    {
        // 更新 RabbitMQ 权限
    }
}

常见问题与解决方案

问题 1:LDAP 连接超时

错误信息

LDAP connection timeout

解决方案

conf
# 增加超时时间
auth_ldap.timeout = 10000

# 配置多个服务器实现高可用
auth_ldap.servers.1 = ldap1.example.com
auth_ldap.servers.2 = ldap2.example.com
auth_ldap.servers.3 = ldap3.example.com

问题 2:用户 DN 查找失败

错误信息

LDAP user lookup failed

解决方案

conf
# 确保正确配置 DN 查找
auth_ldap.dn_lookup_attribute = uid
auth_ldap.dn_lookup_base = ou=Users,dc=example,dc=com
auth_ldap.dn_lookup_filter = (&(objectClass=inetOrgPerson)(uid=${username}))

# 或使用绑定 DN 进行搜索
auth_ldap.bind_dn = cn=admin,dc=example,dc=com
auth_ldap.bind_password = admin_password

问题 3:组映射不生效

解决方案

conf
# 检查标签查询语法
auth_ldap.tag_queries.administrator = {memberOf, "cn=admin-group,ou=Groups,dc=example,dc=com"}

# 启用详细日志进行调试
auth_ldap.log = network,unbound

最佳实践建议

1. 高可用配置

conf
# 配置多个 LDAP 服务器
auth_ldap.servers.1 = ldap-primary.example.com
auth_ldap.servers.2 = ldap-secondary.example.com
auth_ldap.servers.3 = ldap-tertiary.example.com

# 设置合理的超时
auth_ldap.timeout = 5000
auth_ldap.pool_size = 64

2. 安全配置

conf
# 使用 TLS 加密
auth_ldap.use_ssl = true
# 或使用 StartTLS
auth_ldap.use_starttls = true

# 验证服务器证书
auth_ldap.ssl_options.verify = verify_peer
auth_ldap.ssl_options.fail_if_no_peer_cert = true

3. 性能优化

conf
# 启用连接池
auth_ldap.pool_size = 128

# 缓存配置
auth_ldap.cache_ttl = 300000  # 5 分钟

安全注意事项

安全警告

  1. 使用 TLS 加密:LDAP 通信必须加密,防止凭证泄露
  2. 限制绑定账户权限:服务账户只授予必要的读取权限
  3. 定期审计:检查 LDAP 组与 RabbitMQ 权限的映射关系
  4. 监控认证失败:设置告警监控异常认证行为
  5. 备份认证方式:配置链式认证,确保 LDAP 不可用时仍有备用方案

相关链接