安全性
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
,這些屬性檔案僅會在 Broker 啟動時載入!請參閱 AMQ-5876 以獲取詳細資訊。
簡單身份驗證外掛程式
如果您有簡單的身份驗證需求(或只是想快速設定測試環境),您可以使用 SimpleAuthenticationPlugin。使用此外掛程式,您可以直接在 Broker 的 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 版本開始,您可以設定簡單身份驗證外掛程式以允許對 Broker 進行匿名存取。
<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>
若要允許對 Broker 進行匿名存取,請使用 anonymousAccessAllowed
屬性並將其設定為 true
,如上所示。現在,當用戶端在沒有提供使用者名稱和密碼的情況下連線時,將會為其安全性內容分配預設的使用者名稱 (anonymous
) 和群組 (anonymous
)。您可以使用此使用者名稱和密碼來授權用戶端存取適當的 Broker 資源(請參閱下一節)。您也可以使用 anonymousUser
和 anonymousGroup
屬性來變更將指派給匿名 使用者的使用者名稱和群組。
授權
在 ActiveMQ Classic 中,我們使用多種操作,您可以將這些操作與使用者角色以及個別佇列或主題關聯,或者您可以使用萬用字元來附加到主題和佇列的階層。
操作 | 說明 |
---|---|
讀取 | 您可以從目的地瀏覽和使用 |
寫入 | 您可以將訊息傳送到目的地 |
管理 | 如果目的地尚不存在,您可以延遲建立目的地。這讓您可以精細控制可以在佇列/主題階層的哪個部分動態建立哪些新目的地 |
佇列/主題可以使用 ActiveMQ Classic 萬用字元語法指定。
授權範例
以下 範例顯示這 2 個外掛程式的運作方式。但請注意,編寫自己的外掛程式非常容易。請注意,一般而言,應將完全存取權授予 ActiveMQ.Advisory 目的地,因為預設情況下,ActiveMQConnection 會使用目的地通知來及早了解臨時目的地的建立和刪除。此外,動態網路連接器會使用通知來判斷使用者需求。
如有必要,可以透過 ActiveMQConnectionFactory 的 watchTopicAdvisories 布林屬性和網路連接器的網路連接器 staticBridge (5.6) 布林屬性來停用以這種方式使用通知。
Broker 到 Broker 的身份驗證和授權
如果您已為特定的訊息 Broker 啟用身份驗證,則其他想要連線到該 Broker 的 Broker 必須透過其
- Broker 網路包含兩個 Broker (BrokerA 和 BrokerB)
- 已透過範例
<simpleAuthenticationPlugin>
元素為 BrokerA 啟用身份驗證。 - 尚未為 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>
元素的使用者名稱也必須具有適當的授權憑證。如果 BrokerA 已啟用授權,且 BrokerB 相應的 <networkConnector>
元素的使用者名稱未獲得適當的授權憑證,則無法將訊息從 BrokerB 轉寄到 BrokerA。
此外,如果 BrokerA 獲得 <networkConnector>
元素,使其可以起始與 BrokerB 的連線,則必須為該 <networkConnector>
提供在 <simpleAuthenticationPlugin>
元素中定義的使用者名稱/密碼組合;即使 BrokerB 沒有啟用身份驗證服務,也需要這樣做。
控制對臨時目的地的存取
若要控制對臨時目的地的存取,您需要將 <tempDestinationAuthorizationEntry>
元素新增至 authorizationMap
。透過這個元素,您可以控制對所有臨時目的地的存取。如果此元素不存在,則會將臨時目的地的讀取、寫入和管理權限授予所有人。在下面的範例中,臨時目的地的讀取、寫入和管理權限僅授予那些已指派給「管理員」群組的用戶端。
<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 組件
除了訊息 Broker 之外,您還可以選擇性地執行幾個額外的「元件」,例如 Camel 和/或 Web 主控台。這些元件與 Broker 建立連線;因此,如果您已保護您的 Broker(即已啟用身份驗證),您必須設定這些元件,才能讓它們在連線到 Broker 時提供所需的安全性憑證(使用者名稱、密碼)。
Camel
您可能在 Broker 的 XML 組態檔中定義了以下 Camel 環境。
<!-- ** Lets deploy some Enterprise Integration Patterns inside the ActiveMQ Classic Message Broker ** For more details see ** ** https://activemq.dev.org.twFeatures/enterprise-integration-patterns.md -->
<camelContext id="camel" xmlns="https://activemq.dev.org.tw/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 定義新增至 Broker 的 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 會在連線到 Broker 時傳遞指定的安全性憑證。
如果 Broker 在 OSGi 容器中執行,請在 ActiveMQComponent Bean 定義之後新增以下行
<service ref="activemq" interface="org.apache.camel.Component"/>
Web 主控台
如果您想要在安全的 Broker 中使用 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 組態中。此外,還有一個集中位置,您可以在其中設定這些元件將用來連線到 Broker 的憑證。只需在 conf/credentials.properties
檔案中設定您所需的使用者名稱和密碼即可,預設情況下如下所示
activemq.username=system activemq.password=manager
加密的密碼
從 5.4.1 版開始,您也可以將 加密的密碼與您的 Broker 搭配使用
訊息層級授權
也可以使用您選擇的某些基於內容的授權原則來授權每個訊息。與之前描述的其他安全性選項相比,訊息層級授權需要的設定不僅僅是一些組態。您必須先建立一個新的 Maven 專案,並將 activemq-all Maven 相依性(與您的 ActiveMQ 安裝版本相同的版本)新增到新專案的 pom.xml 中。
下一步,您必須建立一個新的 Java 類別,並讓它實作 org.apache.activemq.security.MessageAuthorizationPolicy 介面。之後,只需將一個具有簽名的 方法新增到新的 Java 類別中
public boolean isAllowedToConsume(ConnectionContext context, Message message){...}
若要使用您自己的訊息層級授權原則,Java 類別必須打包為 jar,並新增到 ActiveMQ Classic 的 /lib 資料夾中,使其可用。最後一步,必須使用 *messageAuthorizationPolicy* 屬性直接在 Broker 上設定它,或將其新增到 XML 中,如下所示
<broker>
..
<messageAuthorizationPolicy>
<bean class="com.acme.MyMessageAuthorizationPolicy" xmlns=""/>
</messageAuthorizationPolicy>
..
</broker>
實作您自己的自訂安全性外掛程式
所有各種安全性實作都以 攔截器的形式實作,因此新增您自己的自訂實作非常容易。如果您使用 JAAS,則從 簡單實作之一開始可能會更容易,但您可以從 JAAS 實作派生。