安全
ActiveMQ Classic 4.x 及更高版本通过各种不同的提供者提供可插拔的安全功能。
最常见的提供者是
- JAAS 用于身份验证
- 使用简单的 XML 配置文件进行默认授权机制。
身份验证
默认的 JAAS 插件依赖于标准的 JAAS 机制进行身份验证。请参阅 文档 以获取更多详细信息。
通常,您使用类似 此文件 的配置文件来配置 JAAS,并将 java.security.auth.login.config 系统属性设置为指向它。如果没有指定系统属性,则默认情况下,ActiveMQ Classic JAAS 插件将在类路径中查找 login.config 并使用它。
身份验证示例
这是一个示例 login.config,它指向以下文件
注意:在 5.11.1 版本之前,这些属性文件默认情况下在每次身份验证请求时都会重新加载。因此,对用户、密码和组的更新会立即加载。从 5.12 开始,只有在您的 LoginModule 配置中设置了 reload=true 时,它们才会重新加载,例如:
activemq { org.apache.activemq.jaas.PropertiesLoginModule required org.apache.activemq.jaas.properties.user="users.properties" org.apache.activemq.jaas.properties.group="groups.properties" reload=true; };
如果未设置 reload=true
,则这些属性文件只会在代理启动时加载一次!有关详细信息,请参阅 AMQ-5876。
简单身份验证插件
如果您有适度的身份验证需求(或者只想快速设置您的测试环境),您可以使用 SimpleAuthenticationPlugin。使用此插件,您可以在代理的 XML 配置中直接定义用户和组。例如,请查看以下代码片段
<simpleAuthenticationPlugin>
<users>
<authenticationUser username="system" password="manager" groups="users,admins"/>
<authenticationUser username="user" password="password" groups="users"/>
<authenticationUser username="guest" password="password" groups="guests"/>
</users>
</simpleAuthenticationPlugin>
以这种方式定义的用户和组稍后可以与相应的授权插件一起使用。
匿名访问
从 5.4.0 版本开始,您可以配置简单身份验证插件以允许匿名访问代理。
<simpleAuthenticationPlugin anonymousAccessAllowed="true"> <users> <authenticationUser username="system" password="manager" groups="users,admins"/> <authenticationUser username="user" password="password" groups="users"/> <authenticationUser username="guest" password="password" groups="guests"/> </users> </simpleAuthenticationPlugin>
要允许匿名访问代理,请使用 anonymousAccessAllowed
属性并将其设置为 true
,如上所示。现在,当客户端在未提供用户名和密码的情况下连接时,将为其安全上下文分配默认用户名 (anonymous
) 和组 (anonymous
)。您可以使用此用户名和密码来授权客户端访问相应的代理资源(请参阅下一节)。您还可以使用 anonymousUser
和 anonymousGroup
属性更改将分配给匿名用户的用户名和组。
授权
在 ActiveMQ Classic 中,我们使用许多操作,您可以将这些操作与用户角色以及单个队列或主题相关联,或者您可以使用通配符将这些操作附加到主题和队列的层次结构。
操作 | 描述 |
---|---|
读取 | 您可以浏览和消费来自目的地的消息 |
写入 | 您可以向目的地发送消息 |
管理 | 如果目的地尚不存在,您可以延迟创建它。这允许您对哪些新的目的地可以在队列/主题层次结构的哪个部分动态创建进行细粒度的控制 |
可以使用 ActiveMQ Classic 通配符 语法来指定队列/主题。
授权示例
以下 示例 显示了这两个插件的操作。尽管请注意,编写您自己的插件非常容易。注意,通常应该向 ActiveMQ.Advisory 目的地授予完全访问权限,因为默认情况下,ActiveMQConnection 使用目的地公告来尽早了解临时目的地的创建和删除。此外,动态网络连接器使用公告来确定消费者需求。
如果需要,可以通过 ActiveMQConnectionFactory 的watchTopicAdvisories 布尔属性禁用以这种方式使用公告,对于网络连接器,可以通过网络连接器的staticBridge(5.6) 布尔属性禁用。
代理到代理身份验证和授权
如果您已为特定消息代理启用了身份验证,则希望连接到该代理的其他代理必须通过其
- 代理网络包含两个代理(BrokerA 和 BrokerB)
- BrokerA 的身份验证已通过示例
<simpleAuthenticationPlugin>
元素启用。 - BrokerB 的身份验证未启用。
- BrokerA 仅侦听连接。换句话说,BrokerA 具有
<transportConnector>
元素,但没有<networkConnector>
元素。
为了使 BrokerB 连接到 BrokerA,BrokerB 的 XML 配置文件中的相应
<networkConnectors>
<networkConnector name="brokerAbridge" userName="user" password="password" uri="static://(tcp://brokerA:61616)"/>
</networkConnectors>
请注意,BrokerB 的 <networkConnector>
元素必须提供适当的凭据才能连接到 BrokerA。如果 BrokerA 已启用授权,则分配给 <networkConnector>
元素的 userName 也必须具有适当的授权凭据。如果 BrokerA 已启用授权,并且 BrokerB 的相应 <networkConnector>
元素的 userName 未被授予适当的授权凭据,则消息将无法从 BrokerB 转发到 BrokerA。
此外,如果 BrokerA 被授予 <networkConnector>
元素,以便它可以启动到 BrokerB 的连接,则该 <networkConnector>
必须被授予在 <simpleAuthenticationPlugin>
元素中定义的 userName/password 组合;即使 BrokerB 未启用身份验证服务,这也是必需的。
控制对临时目的地的访问
要控制对临时目的地的访问,您需要将 <tempDestinationAuthorizationEntry>
元素添加到 authorizationMap
中。通过此元素,您可以控制对所有临时目的地的访问。如果此元素不存在,则将向所有用户授予临时目的地的读取、写入和管理权限。在下面的示例中,只有那些被分配到 'admin' 组的客户端才能获得对临时目的地的读取、写入和管理权限。
<broker>
..
<plugins>
..
<authorizationPlugin>
<map>
<authorizationMap>
<authorizationEntries>
<authorizationEntry queue="TEST.Q" read="users" write="users" admin="users" />
<authorizationEntry topic="ActiveMQ.Advisory.>" read="*" write="*" admin="*"/>
</authorizationEntries>
<tempDestinationAuthorizationEntry>
<tempDestinationAuthorizationEntry read="admin" write="admin" admin="admin"/>
</tempDestinationAuthorizationEntry>
</authorizationMap>
</map>
</authorizationPlugin>
..
</plugins>
..
</broker>
使用 JAAS 插件进行 LDAP 身份验证
新模块
从 5.6 开始,提供了一个新的/更好的 ldap 授权模块。有关更多信息,请参阅 缓存的 LDAP 授权模块。
-
在 activemq.xml 中配置 JAAS LDAPLoginModule 和 LDAPAuthorizationMap
<plugins> <!-- use JAAS to authenticate using the login.config file on the classpath to configure JAAS --> <jaasAuthenticationPlugin configuration="LdapConfiguration" /> <!-- lets configure a destination based role/group authorization mechanism --> <authorizationPlugin> <map> <bean xmlns="http://www.springframework.org/schema/beans" id="lDAPAuthorizationMap" class="org.apache.activemq.security.LDAPAuthorizationMap"> <property name="initialContextFactory" value="com.sun.jndi.ldap.LdapCtxFactory"/> <property name="connectionURL" value="ldap://ldap.acme.com:389"/> <property name="authentication" value="simple"/> <property name="connectionUsername" value="cn=mqbroker,ou=Services,dc=acme,dc=com"/> <property name="connectionPassword" value="password"/> <property name="connectionProtocol" value="s"/> <property name="topicSearchMatching" value="cn={0},ou=Topic,ou=Destination,ou=ActiveMQ,ou=systems,dc=acme,dc=com"/> <property name="topicSearchSubtreeBool" value="true"/> <property name="queueSearchMatching" value="cn={0},ou=Queue,ou=Destination,ou=ActiveMQ,ou=systems,dc=acme,dc=com"/> <property name="queueSearchSubtreeBool" value="true"/> <property name="adminBase" value="(cn=admin)"/> <property name="adminAttribute" value="member"/> <property name="adminAttributePrefix" value="cn="/> <property name="readBase" value="(cn=read)"/> <property name="readAttribute" value="member"/> <property name="readAttributePrefix" value="cn="/> <property name="writeBase" value="(cn=write)"/> <property name="writeAttribute" value="member"/> <property name="writeAttributePrefix" value="cn="/> </bean> </map> </authorizationPlugin> </plugins>
- 配置 JAAS login.config(我还没有对配置进行重复数据删除)
LdapConfiguration { org.apache.activemq.jaas.LDAPLoginModule required initialContextFactory=com.sun.jndi.ldap.LdapCtxFactory connectionURL="ldap://ldap.acme.com:389" connectionUsername="cn=mqbroker,ou=Services,dc=acme,dc=com" connectionPassword=password connectionProtocol=s authentication=simple userBase="ou=User,ou=ActiveMQ,ou=systems,dc=acme,dc=com" userRoleName=dummyUserRoleName userSearchMatching="(uid={0})" userSearchSubtree=false roleBase="ou=Group,ou=ActiveMQ,ou=systems,dc=acme,dc=com" roleName=cn roleSearchMatching="(member:=uid={1})" roleSearchSubtree=true ; };
- 将以下 LDIF 文件导入 LDAP 服务器
version: 1 # # Sample LDIF for ActiveMQ LDAP authentication and authorisation # Passwords are defaulted to "password" - it is your responsibility to change them! # # Sets up: # 1. Bind user # 2. A sample queue with admin,read,write permission assignments # 3. ActiveMQ Classic advisory topics # 4. Two groups - admin and webapp # 5. Two users - admin and webapp # 6. Role assignments - admin->admin, webapp->webapp # # (c) Robin Bramley 2008 # Provided as is without any warranty of any kind # dn: dc=acme,dc=com dc: acme objectClass: domain objectClass: top dn: ou=Services,dc=acme,dc=com ou: Services objectClass: organizationalUnit objectClass: top dn: cn=mqbroker,ou=Services,dc=acme,dc=com cn: mqbroker objectClass: organizationalRole objectClass: top objectClass: simpleSecurityObject userPassword: {SSHA}j0NpveEO0YD5rgI5kY8OxSRiN5KQ/kE4 description: Bind user for MQ broker dn: ou=systems,dc=acme,dc=com ou: systems objectClass: organizationalUnit objectClass: top dn: ou=ActiveMQ,ou=systems,dc=acme,dc=com objectClass: organizationalUnit objectClass: top ou: ActiveMQ dn: ou=Destination,ou=ActiveMQ,ou=systems,dc=acme,dc=com objectClass: organizationalUnit objectClass: top ou: Destination dn: ou=Queue,ou=Destination,ou=ActiveMQ,ou=systems,dc=acme,dc=com objectClass: organizationalUnit objectClass: top ou: Queue dn: cn=com.acme.myfirstrealqueue,ou=Queue,ou=Destination,ou=ActiveMQ,ou=syst ems,dc=acme,dc=com cn: com.acme.myfirstrealqueue description: A queue objectClass: applicationProcess objectClass: top dn: cn=admin,cn=com.acme.myfirstrealqueue,ou=Queue,ou=Destination,ou=ActiveM Q,ou=systems,dc=acme,dc=com cn: admin description: Admin privilege group, members are roles member: cn=admin member: cn=webapp objectClass: groupOfNames objectClass: top dn: cn=read,cn=com.acme.myfirstrealqueue,ou=Queue,ou=Destination,ou=ActiveMQ ,ou=systems,dc=acme,dc=com cn: read member: cn=webapp objectClass: groupOfNames objectClass: top dn: cn=write,cn=com.acme.myfirstrealqueue,ou=Queue,ou=Destination,ou=ActiveM Q,ou=systems,dc=acme,dc=com cn: write objectClass: groupOfNames objectClass: top member: cn=webapp dn: ou=Topic,ou=Destination,ou=ActiveMQ,ou=systems,dc=acme,dc=co m objectClass: organizationalUnit objectClass: top ou: Topic dn: cn=ActiveMQ.Advisory.Consumer,ou=Topic,ou=Destination,ou=ActiveMQ,ou=sys tems,dc=acme,dc=com cn: ActiveMQ.Advisory.Consumer objectClass: applicationProcess objectClass: top description: Advisory topic about consumers dn: cn=read,cn=ActiveMQ.Advisory.Consumer,ou=Topic,ou=Destination,ou=ActiveM Q,ou=systems,dc=acme,dc=com cn: read member: cn=webapp objectClass: groupOfNames objectClass: top dn: cn=ActiveMQ.Advisory.TempQueue,ou=Topic,ou=Destination,ou=ActiveMQ,ou=sy stems,dc=acme,dc=com cn: ActiveMQ.Advisory.TempQueue description: Advisory topic about temporary queues objectClass: applicationProcess objectClass: top dn: cn=read,cn=ActiveMQ.Advisory.TempQueue,ou=Topic,ou=Destination,ou=Active MQ,ou=systems,dc=acme,dc=com cn: read member: cn=webapp objectClass: groupOfNames objectClass: top dn: cn=ActiveMQ.Advisory.TempTopic,ou=Topic,ou=Destination,ou=ActiveMQ,ou=sy stems,dc=acme,dc=com cn: ActiveMQ.Advisory.TempTopic objectClass: applicationProcess objectClass: top description: Advisory topic about temporary topics dn: cn=read,cn=ActiveMQ.Advisory.TempTopic,ou=Topic,ou=Destination,ou=Active MQ,ou=systems,dc=acme,dc=com cn: read member: cn=webapp objectClass: groupOfNames objectClass: top dn: ou=Group,ou=ActiveMQ,ou=systems,dc=acme,dc=com objectClass: organizationalUnit objectClass: top ou: Group dn: cn=admin,ou=Group,ou=ActiveMQ,ou=systems,dc=acme,dc=com cn: admin member: uid=admin objectClass: groupOfNames objectClass: top dn: cn=webapp,ou=Group,ou=ActiveMQ,ou=systems,dc=acme,dc=com cn: webapp member: uid=webapp objectClass: groupOfNames objectClass: top dn: ou=User,ou=ActiveMQ,ou=systems,dc=acme,dc=com objectClass: organizationalUnit objectClass: top ou: User dn: uid=admin,ou=User,ou=ActiveMQ,ou=systems,dc=acme,dc=com uid: admin userPassword: {SSHA}j0NpveEO0YD5rgI5kY8OxSRiN5KQ/kE4 objectClass: account objectClass: simpleSecurityObject objectClass: top dn: uid=webapp,ou=User,ou=ActiveMQ,ou=systems,dc=acme,dc=com uid: webapp userPassword: {SSHA}j0NpveEO0YD5rgI5kY8OxSRiN5KQ/kE4 objectClass: account objectClass: simpleSecurityObject objectClass: top
-
启动 ActiveMQ Classic
- 测试它
安全性和 ActiveMQ Classic 组件
除了消息代理之外,您还可以选择执行几个额外的“组件”,例如 Camel 和/或 Web 控制台。这些组件与代理建立连接;因此,如果您已保护您的代理(即已启用身份验证),则必须配置这些组件,以便它们在连接到代理时提供所需的安全性凭据(用户名、密码)。
骆驼
您可能在代理的 XML 配置文件中定义了以下 Camel 上下文。
<!-- ** Lets deploy some Enterprise Integration Patterns inside the ActiveMQ Classic Message Broker ** For more details see ** ** https://activemq.apache.ac.cnFeatures/enterprise-integration-patterns.md -->
<camelContext id="camel" xmlns="https://activemq.apache.org/camel/schema/spring">
<package>org.foo.bar</package>
<route>
<from uri="activemq:example.A"/>
<to uri="activemq:example.B"/>
</route>
</camelContext>
以上配置未设置为在安全环境中工作。
如果应用程序在 OSGi 容器中运行,请在 CamelContext 定义之前添加以下行
<osgi:reference id="activemq" interface="org.apache.camel.Component" />
这允许容器中部署的任何预先配置的 ActiveMQComponent 实例优先于默认的 ActiveMQComponent。
也就是说,使用以上配置,Camel 将与 ActiveMQ Classic 建立连接,但不会提供用户名和密码。因此,当启用 ActiveMQ Classic 安全性时,以上配置会导致安全性异常。异常将被多次抛出,因为 Camel 将继续重试连接。如果您未使用 Camel,请注释掉以上 XML 代码。如果您正在使用 Camel,请将以下 bean 定义添加到代理的 XML 配置中
<!-- configure the camel activemq component to use the current broker -->
<bean id="activemq" class="org.apache.activemq.camel.component.ActiveMQComponent" >
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://127.0.0.1?create=false&waitForStart=10000" />
<property name="userName" value="system"/>
<property name="password" value="manager"/>
</bean>
</property>
</bean>
使用以上 bean 定义,Camel 将在连接到代理时传递指定的安全性凭据。
如果代理在 OSGi 容器中运行,请在 ActiveMQComponent bean 定义之后添加以下行
<service ref="activemq" interface="org.apache.camel.Component"/>
网络控制台
如果您想在安全代理中使用 Web 控制台,您必须更改 webapps/admin/WEB-INF/webconsole-embeded.xml
中的 connectionFactory
bean,使其类似于以下内容
<bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="vm://127.0.0.1"/>
<property name="userName" value="system"/>
<property name="password" value="manager"/>
</bean>
默认凭据
从 5.3 版本开始,所有以上配置详细信息都包含在默认的 ActiveMQ Classic 配置中。此外,还有一个中央位置,您可以在其中设置这些组件将用于连接到代理的凭据。只需在 conf/credentials.properties
文件中设置所需的用户名和密码,该文件默认情况下看起来像这样
activemq.username=system activemq.password=manager
加密密码
从 5.4.1 版本开始,您还可以对代理使用 加密密码
消息级别授权
还可以使用您选择的基于内容的授权策略来授权每条单独的消息。与之前描述的其他安全选项相比,消息级别授权需要比一些配置更多的东西。您必须从创建一个新的 Maven 项目开始,并将 activemq-all Maven 依赖项(与您的 activemq 安装相同版本)添加到新项目的 pom.xml 中。
在下一步中,您必须创建一个新的 Java 类,并让它实现 org.apache.activemq.security.MessageAuthorizationPolicy 接口。之后,只需添加一个具有以下签名的方法
public boolean isAllowedToConsume(ConnectionContext context, Message message){...}
到新的 Java 类中。为了使用您自己的消息级别授权策略,Java 类必须打包为 jar 并添加到 ActiveMQ Classic 的 /lib 文件夹中,以使其可用。在最后一步中,必须通过使用 *messageAuthorizationPolicy* 属性或将其添加到 XML 中,将其直接配置到代理上,如下所示
<broker>
..
<messageAuthorizationPolicy>
<bean class="com.acme.MyMessageAuthorizationPolicy" xmlns=""/>
</messageAuthorizationPolicy>
..
</broker>
实现您自己的自定义安全插件
所有各种安全性实现都是作为 拦截器 实现的,因此添加您自己的自定义实现非常容易。如果使用 JAAS,您可能更容易从 简单实现 之一入手,尽管您可以从 JAAS 实现 中派生。