RabbitMQ有用一套专门的权限控制系统,用来控制不同用户对不同虚拟主机的访问控制。基本思路同大多系统一样:先创建用户,然后为用户授予权限。

大多数系统并没有严格区分认证和鉴权,但是在RabbitMQ中,对这两个概念做了明确的区分:

  • 认证:识别用户身份,即:识别当前用户,认证其身份信息

  • 鉴权:明确了用户身份,那么授权就是要检查该用户是否拥有相应的权限,即:检查授予用户的权限

1. 虚拟主机和guest用户

RabbitMQ基础(七)----虚拟主机vhost一篇,我们已经详细介绍了虚拟主机。我们说过,首次安装好RabbitMQ时,会拥有一个默认的虚拟主机“/”。同时,还有一个默认的用户“guest”,密码也为“guest”,该用户有用默认虚拟主机的全部权限。

如果你得RabbitMQ需要公网访问,出于安全性考虑,官方建议删掉该用户或者修改其密码。默认情况下,RabbitMQ禁止guest用户远程访问,只可以访问本地的mq服务。这个是通过loopback_users配置项决定的,如果需要取消该限制,仅需将该选项配置为none即可:

loopback_users = none

如果是3.7之前的版本,还不支持这样key=value的配置格式,那么你需要配置成这样:

``[{rabbit, [{loopback_users, []}]}].``

该配置想的意思是,取消所有用户的本地访问限制。按照 官方的示例配置文件,如果仅需要取消guest用户本地访问限制,那么进行以下配置:

loopback_users.guest = false

否则设置为true即可。

关于MQ的配置文件,我们将在后续博文中详细讨论,官方文档见 这里

2. 权限工作机制

当客户端与服务端建立连接时,第一级权限控制将会执行:服务器会检查连接的用户是否有用访问其连接的虚拟主机的权限,没有则会拒绝连接,否则连接建立成功。

用户操作虚拟主机的资源(路由器、队列、绑定等)时,RabbitMQ会启用第二级权限控制,验证用户是否具有访问虚拟机的资源的权限。

2.1. 权限定义

具体而言,在RabbitMQ中,对资源的操作定义了三种权限:

  • 配置:创建和删除资源,或者改变它们的行为;

  • 写:发布消息到资源;

  • 读:从资源获取消息;

下表显示了对执行权限检查的所有AMQP命令所需的资源类型的权限:

AMQP 0-9-1 Operationconfigurewriteread

exchange.declare

(passive=false)

exchange

exchange.declare

(passive=true)

 

 

 

exchange.declare

(with  AE)

exchange

exchange (AE)

exchange

exchange.delete

 

exchange

 

 

queue.declare

(passive=false)

queue

 

 

queue.declare

(passive=true)

 

 

 

queue.declare

(with  DLX)

queue

exchange (DLX)

queue

queue.delete

 

queue

 

 

exchange.bind

 

 

exchange (destination)

exchange (source)

exchange.unbind

 

 

exchange (destination)

exchange (source)

queue.bind

 

 

queue

exchange

queue.unbind

 

 

queue

exchange

basic.publish

 

 

exchange

 

basic.get

 

 

 

queue

basic.consume

 

 

 

queue

queue.purge

 

 

 

queue

说明:

  • passive的理解:passive意思是被动的,当设置为true,那么交换器或者队列存在,则返回true,否则会抛出异常,但是不会创建新的交换器或队列;

  • AE:Alternate Exchanges,备用交换器

  • DLX:Dead Letter Exchanges,死信交换器

2.2. 权限表示

RabbitMQ中,权限使用三个正则表达式来分别代表虚拟主机的配置、写、读权限,这些正则表达式用来匹配授权用户所访问的资源(交换器、队列和绑定),格式如下:

`config表达式' 'write表达式' 'read表达式'

例如:

  • '.*' '.*' '.*':这个表达式的权限设置表示,用户拥有操作资源的全部权限;

注意
RabbitMQ可能缓存每个连接或每个通道的访问控制检查结果。因此,对用户权限的更改可能只在用户重新连接时才会生效。

3. Topic授权

从3.7版本开始,RabbitMQ开始支持为topic交换器设置权限,在设置权限时,要结合发布到主题交换器的消息的routing key信息(例如,在RabbitMQ默认授权后端,routing key与正则表达式是否匹配,决定了消息是否可以路由到下游)。

设置了topic授权后,向topic路由器发布消息,或者从topic路由器接收消息,都会依据消息的routing key进行权限检查,如果routing key与权限设置匹配,则成功,否则抛出异常。

看下边的一个例子,假如有一个虚拟机名称为vhost1,具有全部权限的用户vhost1,现在我设置一下topic权限如下:

rabbitmqctl.bat set_topic_permissions -p vhost1 vhost1 topicExchange "^log.*" "^log.*"

上边的含义是:用户vhost1在vhost1虚拟机上的topicExchange交换器具备如下权限:能够发布和消费以"log."开头的消息,关键测试代码如下:

TopicACLServer
public static final String VHOST = "vhost1";
public static final String TOPIC_EXCHANGE = "topicExchange";
public static final String USER_NAME = "vhost1";
public static final String PWD = "123456";

public static void main(String[] args) throws IOException, TimeoutException {
    ConnectionFactory factory = new ConnectionFactory();
    factory.setVirtualHost(VHOST);
    factory.setUsername(USER_NAME);
    factory.setPassword(PWD);
    Connection conn = factory.newConnection();
    Channel channel = conn.createChannel();

    channel.exchangeDeclare(TOPIC_EXCHANGE, "topic");
    channel.basicPublish(TOPIC_EXCHANGE, "log.controller", null,
                "this is controller log".getBytes("utf-8"));
    channel.basicPublish(TOPIC_EXCHANGE, "log.service", null,
                "this is service log".getBytes("utf-8"));
    channel.basicPublish(TOPIC_EXCHANGE, "log.model", null,
                "this is model log".getBytes("utf-8"));
    // 无法成功发布消息:reply-text=ACCESS_REFUSED - access to topic 'other\.*' in exchange,即没有该routing key的权限
    // channel.basicPublish(TOPIC_EXCHANGE, "other.key", null,
    //     "this is other log".getBytes("utf-8"));
    channel.close();
    conn.close();
 }
TopicACLClient
ConnectionFactory factory = new ConnectionFactory();
factory.setVirtualHost(VHOST);
factory.setUsername(USER_NAME);
factory.setPassword(PWD);
Connection conn = factory.newConnection();
Channel channel = conn.createChannel();
channel.exchangeDeclare(TOPIC_EXCHANGE, "topic");
String queueName = channel.queueDeclare().getQueue();
channel.queueBind(queueName, TOPIC_EXCHANGE, "log.*");
// 无法消费:reply-text=ACCESS_REFUSED - access to topic 'other\.*' in exchange,即没有该routing key的权限
// channel.queueBind(queueName, TOPIC_EXCHANGE, "other-*");
channel.basicConsume(queueName, true, new DefaultConsumer(channel) {
    @Override
    public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
        System.out.println("收到消息:" + new String(body, "utf-8"));
    }
});

当在发布消息或者消费消息时,如果routing key不是以log.开头,则直接抛出异常:

'config表达式' 'write表达式' 'read表达式'

4. 设置权限

其实在 "RabbitMQ基础(七)--虚拟主机vhost" 一篇中,我们已经简单介绍了权限的相关操作了。要设置权限,需要在rabbitmqctl控制台进行,基本思路:创建虚拟机、创建用户、为用户授予权限。

4.1. 用户管理

Rabbitmqctl管理RabbitMQ内部用户数据库,来自任何其他身份验证后端的用户对rabbitmqctl都不可见。

  • 添加用户:

add_user username password
  • username 被创建的用户名

  • password 用于对应的访问密码

  • 删除用户

delete_user username
  • username 被删除的用户名

  • 修改密码

change_password username newpassword
  • username 被修改密码的用户名

  • newpassword 新密码

  • 清除密码

clear_password username
  • username 被清除密码的用户名 备注:清除密码后,用户无法登录

  • 用户校验

authenticate_user username password
  • username 被校验的用户名

  • password 被校验的密码

验成功则返回success,否则出现错误信息。

  • 为用户做标签

set_user_tags username [tag ...]
  • username 被标记的用户名

  • tag 零个或多个标签

e.g. rabbitmqctl set_user_tags tonyg administrator

  • 查询用户

list_users

列出所有用户,结果的每一行显示为用户名和用户的标签

4.2. 虚拟机管理

  • 添加虚拟机

add_vhost vhost

vhost: 虚拟机名称

  • 删除虚拟机

delete_vhost vhost

host: 被删除的虚拟机名称

  • 查询虚拟机

list_vhosts [vhostinfoitem ...]
vhostinfoitem

用于标识在结果中包含哪些虚拟主机信息项,结果中的列顺序将与参数的顺序匹配。vhostinfoitem可以从以下列表中获取任何值:

name

虚拟机的名称

tracing

虚拟机是否开启tracing

e.g. rabbitmqctl list_vhosts name tracing

4.3. 授予权限

  • 授权

set_permissions [-p vhost] user conf write read

vhost 被授权的虚拟机,默认是“/”
user 被授权的用户
conf 允许具有配置权限的资源正则表达式
write 允许具有写权限的资源正则表达式
read 允许具有读权限的资源正则表达式

e.g. rabbitmqctl set_permissions -p /myvhost tonyg “^tonyg-.” “.” “.*”

含义:用户tonyg具有虚拟机myvhost的写、读的全部权限,但是仅有名称以tonyg-开头的资源的配置权限。

  • 清除权限

clear_permissions [-p vhost] username

vhost 目标虚拟机
username 被清除权限的用户
  • 虚拟机权限查询

list_permissions [-p vhost]

vhost 目标虚拟机名称

以虚拟机为基础,查询虚拟机的访问权限,结果以用户和权限展示。

  • 用户权限查询

list_user_permissions username

username 被查询的用户名

以用户为基础,查询用户的权限,结果以虚拟机和权限展示。

  • topic权限设置

set_topic_permissions [-p vhost] user exchange write read

vhost 虚拟机名称
user 用户名
exchange 交换器
write 写权限
read 读权限

e.g.

rabbitmqctl set_topic_permissions -p /myvhost tonyg amq.topic “^tonyg-.*” “^tonyg-.*”

用户tonyg能够从myvhost虚拟机的amq.topic交换器发布和消费消息,并且这些消息的routing key必须以tonyg开头。

  • 清除topic权限

clear_topic_permissions [-p vhost] username [exchange]

vhost 虚拟机名称,默认是“/”
username 用户名
exchange topic交换器名称,不设置则为username用户授权的所有topic交换器
  • 虚拟机的topic权限查询

list_topic_permissions [-p vhost]

vhost 虚拟机名称

结果列为用户 topic交换器 权限

  • 用户的topic权限查询

list_user_topic_permissions username

username 用户名

结果列为虚拟机 topic交换器 权限。

5. 总结

RabbitMQ有一套独立的权限机制:配置、写、读,通过三个正则表达式来分别匹配所访问资源的这三项权限。权限的设置需要使用rabbitmqctl控制台工具来输入命令完成,基本思路是:创建虚拟机、创建用户,然后给用户授予权限。需要注意的是,虽然默认的guest用户有用默认虚拟机"/"的全部权限,但是出于安全考虑,它仅能通过本地访问,要取消该限制,还需要修改rabbitmq的配置文件。

此外,rabbitmq还支持为topic交换器设置消息权限,该权限基于消息的routing key,通过正则匹配消息的routing key来控制具体消息的发布和消费。


相关阅读