<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<html>
<head>
<title>ActiveMQ Artemis JMS Security Example</title>
<link rel="stylesheet" type="text/css" href="../../../common/common.css" />
<link rel="stylesheet" type="text/css" href="../../../common/prettify.css" />
<script type="text/javascript" src="../../../common/prettify.js"></script>
</head>
<body onload="prettyPrint()">
<h1>JMS Security LDAP Example</h1>
<pre>To run the example, simply type <b>mvn verify</b> from this directory, <br>or <b>mvn -PnoServer verify</b> if you want to start and create the server manually.</pre>
<p>This example shows how to configure and use security using ActiveMQ Artemis and the Apache DS LDAP server.</p>
<p>With security properly configured, ActiveMQ Artemis can restrict client access to its resources, including
connection creation, message sending/receiving, etc. This is done by configuring users and roles as well as permissions in
the configuration files. </p>
<p>ActiveMQ Artemis supports wild-card security configuration. This feature makes security configuration very
flexible and enables fine-grained control over permissions in an efficient way.</p>
<p>For a full description of how to configure security with ActiveMQ Artemis, please consult the user
manual.</p>
<p>This example demonstrates how to configure users/roles in the Apache DS LDAP server, how to configure topics with
proper permissions using wild-card expressions, and how they take effects in a simple program.</p>
<p>Users and roles are configured in Apache DS. The SecurityExample class will start an embedded version of Apache
DS and load the contents of example.ldif which contains the users and passwords for this example.</p>
<pre class="prettyprint">
<code>
dn: dc=activemq,dc=org
dc: activemq
objectClass: top
objectClass: domain
dn: uid=bill,dc=activemq,dc=org
uid: bill
userPassword: activemq
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=andrew,dc=activemq,dc=org
uid: andrew
userPassword: activemq1
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=frank,dc=activemq,dc=org
uid: frank
userPassword: activemq2
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
dn: uid=sam,dc=activemq,dc=org
uid: sam
userPassword: activemq3
objectClass: account
objectClass: simpleSecurityObject
objectClass: top
###################
## Define roles ##
###################
dn: cn=user,dc=activemq,dc=org
cn: user
member: uid=bill,dc=activemq,dc=org
member: uid=andrew,dc=activemq,dc=org
member: uid=frank,dc=activemq,dc=org
member: uid=sam,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=europe-user,dc=activemq,dc=org
cn: europe-user
member: uid=andrew,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=news-user,dc=activemq,dc=org
cn: news-user
member: uid=frank,dc=activemq,dc=org
member: uid=sam,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
dn: cn=us-user,dc=activemq,dc=org
cn: us-user
member: uid=frank,dc=activemq,dc=org
objectClass: groupOfNames
objectClass: top
</code>
</pre>
<p>
User name and password consists of a valid account that can be used to establish connections to a ActiveMQ Artemis server, while
roles are used in controlling the access privileges against ActiveMQ Artemis topics and queues. You can achieve this control by
configuring proper permissions in <code>broker.xml</code>, like the following
</p>
<pre class="prettyprint"><code>
<security-settings>
<!-- any user can have full control of generic topics -->
<security-setting match="jms.topic.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="user"/>
<permission type="consume" roles="user"/>
</security-setting>
<security-setting match="jms.topic.news.europe.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="europe-user"/>
<permission type="consume" roles="news-user"/>
</security-setting>
<security-setting match="jms.topic.news.us.#">
<permission type="createDurableQueue" roles="user"/>
<permission type="deleteDurableQueue" roles="user"/>
<permission type="createNonDurableQueue" roles="user"/>
<permission type="deleteNonDurableQueue" roles="user"/>
<permission type="send" roles="us-user"/>
<permission type="consume" roles="news-user"/>
</security-setting>
</security-settings>
</code></pre>
<p>Permissions can be defined on any group of queues, by using a wildcard. You can easily specify
wildcards to apply certain permissions to a set of matching queues and topics. In the above configuration
we have created four sets of permissions, each set matches against a special group of targets, indicated by wild-card match attributes.</p>
<p>You can provide a very broad permission control as a default and then add more strict control
over specific addresses. By the above we define the following access rules:</p>
<li>Only role 'us-user' can create/delete and pulish messages to topics whose names match wild-card pattern 'news.us.#'.</li>
<li>Only role 'europe-user' can create/delete and publish messages to topics whose names match wild-card pattern 'news.europe.#'.</li>
<li>Only role 'news-user' can subscribe messages to topics whose names match wild-card pattern 'news.us.#' and 'news.europe.#'.</li>
<li>For any other topics that don't match any of the above wild-card patterns, permissions are granted to users of role 'user'.</li>
<p>To illustrate the effect of permissions, three topics are deployed. Topic 'genericTopic' matches 'jms.topic.#' wild-card, topic 'news.europe.europeTopic' matches
jms.topic.news.europe.#' wild-cards, and topic 'news.us.usTopic' matches 'jms.topic.news.us.#'.</p>
<h2>Example step-by-step</h2>
<p><i>To run the example, simply type <code>mvn verify</code> from this directory</i></p>
<ol>
<li>First we need to get an initial context so we can look-up the JMS connection factory and destination objects from JNDI. This initial context will get it's properties from the <code>client-jndi.properties</code> file in the directory <code>../common/config</code></li>
<pre class="prettyprint">
<code>
InitialContext initialContext = getContext(0);
</code>
</pre>
<li>We perform lookup on the topics</li>
<pre class="prettyprint">
<code>
Topic genericTopic = (Topic) initialContext.lookup("/topic/genericTopic");
Topic europeTopic = (Topic) initialContext.lookup("/topic/europeTopic");
Topic usTopic = (Topic) initialContext.lookup("/topic/usTopic");
</code>
</pre>
<li>We perform a lookup on the Connection Factory</li>
<pre class="prettyprint">
<code>
ConnectionFactory cf = (ConnectionFactory) initialContext.lookup("/ConnectionFactory");
</code>
</pre>
<li>We try to create a JMS Connection without user/password. It will fail.</li>
<pre class="prettyprint">
<code>
try
{
cf.createConnection();
result = false;
}
catch (JMSSecurityException e)
{
System.out.println("Default user cannot get a connection. Details: " + e.getMessage());
}
</code>
</pre>
<li>Bill tries to make a connection using wrong password</li>
<pre class="prettyprint">
<code>
billConnection = null;
try
{
billConnection = createConnection("bill", "activemq1", cf);
result = false;
}
catch (JMSException e)
{
System.out.println("User bill failed to connect. Details: " + e.getMessage());
}
</code>
</pre>
<li>Bill makes a good connection.</li>
<pre class="prettyprint">
<code>
billConnection = createConnection("bill", "activemq", cf);
billConnection.start();
</code>
</pre>
<li>Andrew makes a good connection</li>
<pre class="prettyprint">
<code>
andrewConnection = createConnection("andrew", "activemq1", cf);
andrewConnection.start();
</code>
</pre>
<li>Frank makes a good connection</li>
<pre class="prettyprint">
<code>
frankConnection = createConnection("frank", "activemq2", cf);
frankConnection.start();
</code>
</pre>
<li>Sam makes a good connection</li>
<pre class="prettyprint">
<code>
samConnection = createConnection("sam", "activemq3", cf);
samConnection.start();
</code>
</pre>
<li>We check every user can publish/subscribe genericTopics</li>
<pre class="prettyprint">
<code>
checkUserSendAndReceive(genericTopic, billConnection, "bill");
checkUserSendAndReceive(genericTopic, andrewConnection, "andrew");
checkUserSendAndReceive(genericTopic, frankConnection, "frank");
checkUserSendAndReceive(genericTopic, samConnection, "sam");
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for bill: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(europeTopic, billConnection, "bill", andrewConnection, frankConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for andrew: can send but can't receive</li>
<pre class="prettyprint">
<code>
checkUserSendNoReceive(europeTopic, andrewConnection, "andrew", frankConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for frank: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(europeTopic, frankConnection, "frank", andrewConnection);
</code>
</pre>
<li>We check permissions on news.europe.europeTopic for sam: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(europeTopic, samConnection, "sam", andrewConnection);
</code>
</pre>
<li>We check permissions on news.us.usTopic for bill: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(usTopic, billConnection, "bill");
</code>
</pre>
<li>We check permissions on news.us.usTopic for andrew: can't send and can't receive</li>
<pre class="prettyprint">
<code>
checkUserNoSendNoReceive(usTopic, andrewConnection, "andrew");
</code>
</pre>
<li>We check permissions on news.us.usTopic for frank: can both send and receive</li>
<pre class="prettyprint">
<code>
checkUserSendAndReceive(usTopic, frankConnection, "frank");
</code>
</pre>
<li>We check permissions on news.us.usTopic for sam: can't send but can receive</li>
<pre class="prettyprint">
<code>
checkUserReceiveNoSend(usTopic, samConnection, "sam", frankConnection);
</code>
</pre>
<li>And finally, <b>always</b> remember to close your JMS connections and resources after use, in a <code>finally</code> block. Closing a JMS connection will automatically close all of its sessions, consumers, producer and browser objects</li>
<pre class="prettyprint">
<code>
finally
{
if (billConnection != null)
{
billConnection.close();
}
if (andrewConnection != null)
{
andrewConnection.close();
}
if (frankConnection != null)
{
frankConnection.close();
}
if (samConnection != null)
{
samConnection.close();
}
// Also the initialContext
if (initialContext != null)
{
initialContext.close();
}
}
</code>
</pre>
</ol>
</body>
</html>