Shiro

Features > Security > Shiro

ActiveMQ Classic 5.10 and later provides a fully customizable security experience using Apache Shiro.

The ActiveMQ Classic Shiro plugin can secure the ActiveMQ Classic broker, from authenticating transport connections to authorizing behavior with topics and queues and everything in between.

Quickstart

The fastest/simplest way to enable the ShiroPlugin is to define it as a Spring bean in the broker plugins section and embed Shiro ini configuration:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

    <broker xmlns="http://activemq.apache.org/schema/core" ... other attributes here ...>
        <plugins>
            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="iniConfig"><value>

                [main]
                # Shiro object graph configuration here if desired/necessary

                [users]
                # users section format:
                #
                # username = password [, assignedRole1, assignedRole2, ..., assignedRoleN]
                #
                # for example:
                #
                # scott = tiger, advisory, users, administrators
                #
                # Roles and permissions assigned to roles are defined in the [roles] section
                # below. By transitive association, any user assigned a role is granted the
                # role's permissions.
                
                # ActiveMQ Classic System User
                # needed for in-VM/local connections when authentication is enabled:
                system = manager, system
                
                # Other users here.  You should almost always add the \`advisory\` role for each
                # user to make your life easy!  See the [roles] comments below for more info.
                # jsmith = jsmithsPassword, advisory
                # djones = djonesPassword, advisory, ...
                # etc.

                [roles]
                # roles section format:
                #
                # roleName = wildcardPermission1, wildcardPermission2, ..., wildcardPermissionN
                #
                # The 'system' role is assigned all permissions (*).  Be careful when assigning
                # this to actual users other than then system user:
                system = *

                # Full access rights should generally be given to the ActiveMQ.Advisory.*
                # destinations because by default an ActiveMQConnection uses advisory topics to
                # get early knowledge of temp destination creation and deletion. For more info:
                #
                #   http://activemq.apache.org/Features/security.md
                #
                # So we create an 'advisory' role here with a wildcard/catch-all permissions
                # for all advisory topics.  To make your life easy, ensure you assign this to
                # any/all users in the [users] section above, e.g.
                #
                #   jsmith = jsmithsPassword, advisory, ...

                advisory = topic:ActiveMQ.Advisory*

                </value></property>
            </bean>
        </plugins>
    </broker>
</beans>

This config assumes you have a simple/small set of static users that access your ActiveMQ Classic broker. We’ll cover enabling more advanced user repositories later.

Encrypted Passwords

The above example uses plaintext passwords, which is simple to set up and easy to use for testing, but not really secure. Most production deployments will likely want to use encrypted passwords. For example:

<bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
    <!-- enabled by default.  To disable, uncomment:
    <property name="iniConfig"><value>

    [main]
    # Shiro object graph configuration here
    passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
    iniRealm.credentialsMatcher = $passwordMatcher
 
    [users]
    scott = $shiro1$SHA-256$500000$eWpVX2tGX7WCP2J+jMCNqw==$it/NRclMOHrfOvhAEFZ0mxIZRdbcfqIBdwdwdDXW2dM=, advisory
    system = $shiro1$SHA-256$500000$eUyGwMGr9GYzB/gg/MoNgw==$WGc0yWFWv8+hLqjzVLgW7Hat2FQTywDXBl5izpqaLSY=, system

    [roles]
    system = *
    advisory = topic:ActiveMQ.Advisory*
    </value></property>
</bean>

As you can see, two things are different than the simpler/default configuration:

  1. The [main] section configured a PasswordMatcher on the implicit iniRealm. This indicates that all .ini-configured users are expected to have proper hashed/secure passwords.
  2. The [users] lines now have hash values in the password location instead of plaintext values.

To get the hashed password text values, you will want to Download Shiro’s Command Line Hasher from Maven Central. Once downloaded, you can use it to create secure password hashes that you can safely copy-and-paste in to the [users] section:

$ java -jar shiro-tools-hasher-X.X.X-cli.jar -p

It will then ask you to enter the password and then confirm it:

Password to hash:
Password to hash (confirm):

When this command executes, it will print out the securely-salted-iterated-and-hashed password. For example:

$shiro1$SHA-256$500000$eWpVX2tGX7WCP2J+jMCNqw==$it/NRclMOHrfOvhAEFZ0mxIZRdbcfqIBdwdwdDXW2dM=

Take this value and place it as the password in the user definition line (followed by any desired roles, such as the advisory role). For example:

[users]
scott = $shiro1$SHA-256$500000$eWpVX2tGX7WCP2J+jMCNqw==$it/NRclMOHrfOvhAEFZ0mxIZRdbcfqIBdwdwdDXW2dM=, advisory
system = $shiro1$SHA-256$500000$eUyGwMGr9GYzB/gg/MoNgw==$WGc0yWFWv8+hLqjzVLgW7Hat2FQTywDXBl5izpqaLSY=, system

Configuration

The ActiveMQ Classic Shiro plugin can be configured in a number of ways. For example, with Java:

BrokerService brokerService = new BrokerService();

ShiroPlugin shiroPlugin = new ShiroPlugin();
//configure shiroPlugin via getters/setters here

broker.setPlugins(new BrokerPlugin[]{shiroPlugin});
//continue configuring the brokerService as necessary ...

Or, if using traditional ActiveMQ Classic xml, as a Spring bean in the broker plugins section. For example:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:amq="http://activemq.apache.org/schema/core"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

    <broker xmlns="http://activemq.apache.org/schema/core" ... other attributes here ...>

        <plugins>
    
            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <!-- Config properties via getters/setters as necessary: -->
            </bean>

        </plugins>

    </broker>
</beans>

The remaining configuration examples on this page will be shown as bean XML, but know that the same configuration can be done in Java as standard JavaBeans-compatible getter and setter methods.

Enabling/Disabling

You can enable or disable the ShiroPlugin entirely without having to remove it from your configuration. This is convenient when testing, or when you want to enable or disable it based on a configuration parameter at startup.

<bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
    <!-- enabled by default.  To disable, uncomment:
    <property name="enabled" value="false"/> -->
</bean>

A nice technique is to use Spring’s PropertySourcesPlaceholderConfigurer and placeholder tokens (set shiro.enabled = true in one of your placeholder property files):

<beans ...>

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
       ...
    </bean>

    <broker ...>
        <plugins ...>

            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="enabled" value="${shiro.enabled}"/>
            </bean>
 
        </plugins>
    </broker>
</beans>

This allows you to enable or disable the Shiro plugin by simply setting a property in a .properties file without having to change your XML config.

Shiro Environment

The shiroPlugin requires a Shiro Environment to function. You must either configure the plugin with:

  • an Environment instance (or a Shiro SecurityManager instance) that you instantiate and configure elsewhere - e.g. in Java code or elsewhere in the Spring XML config, or
  • specify some Shiro .ini configuration, either as a direct String, an Ini instance, or a resource path where your shiro.ini file is located. The plugin will load the ini config and create an Environment automatically.

Custom Environment

A Shiro Environment object contains everything that Shiro needs to operate, and this encapsulates the Shiro SecurityManager as well. If you want to construct and configure an Environment instance yourself:

<beans ...>
    <broker ...>
        <plugins>
        
            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="environment" ref="shiroEnvironment"/>
            </bean>
 
        </plugins>
    </broker>

    <bean id="shiroEnvironment" class="..">
        ... config here ...
    </bean>
    <bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

Custom SecurityManager

Instead of configuring an Environment instance, you can construct a SecurityManager instead:

<beans ...>
    <broker ...>
        <plugins>
        
            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="securityManager" ref="shiroSecurityManager"/>
            </bean>
 
        </plugins>
    </broker>

    <bean id="shiroSecurityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">
        <property name="realms">
            <list>
                <bean id="myRealm" class="...">
                    ...
                </bean>
                ... maybe more Realm beans ...
            </list>
        </property>
    </bean>
    <bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

If specifying a SecurityManager instead of the Environment property, an Environment will be created automatically that wraps the configured SecurityManager.

shiro.ini File

If you don’t want to construct a SecurityManager or Environment in code or xml, you can easily specify a shiro.ini file instead and an Environment/SecurityManager will automatically be created based on that:

<beans ...>
    <broker ...>
        <plugins>

            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="iniResourcePath" value="classpath:myshiro.ini"/>
            </bean>

        </plugins>
    </broker>
</beans>

This allows you to keep your Shiro config separate from your ActiveMQ Classic broker configuration if you prefer.

shiro.ini Embedded

If you want to use ini configuration and you would prefer to have all configuration in one place, you can embed the ini config instead:

<beans ...>
    <broker ...>
        <plugins ...>
        
            <bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
                <property name="iniConfig">
                    <value>
                    [main]

                    # Shiro object graph configuration here if desired/necessary

                    [users]
                    system = manager, system

                    [roles]
                    system = *
                    advisory = topic:ActiveMQ.Advisory*
                    </value>
                </property>
            </bean>

        </plugins>
    </broker>
</beans>

Design

The Shiro plugin is a BrokerPlugin that inserts 3 BrokerFilters in the broker filter chain: the SubjectFilter, the AuthenticationFilter and the AuthorizationFilter

SubjectFilter

The SubjectFilter exists before all other Shiro-related broker filters in the broker filter chain. It constructs a Shiro Subject instance reflecting the broker client and ensures the Subject instance is available for all downstream broker filters that may need to use the Subject to perform security operations.

AuthenticationFilter

The AuthenticationFilter exists immediately after the SubjectFilter in the broker filter chain. It ensures that the broker client Subject is authenticated if necessary before allowing the chain to continue. If authentication is required and the Subject is not authenticated, the broker filter chain will not be executed, ensuring only verified identities may perform further behavior.

AuthorizationFilter

The AuthorizationFilter exists immediately after the AuthenticationFilter in the broker filter chain. It ensures that the Subject associated with the filter chain is authorized (permitted) to perform the action being attempted before allowing the action to execute.

For example, it would ensure that the Subject is allowed to send a message to a particular topic before allowing the send operation to execute. If authorization is enabled and the Subject is not authorized to perform the desired action, the broker filter chain will not be executed.

SubjectFilter

The ShiroPlugin installs and executes the SubjectFilter before all other Shiro-related broker filters in the broker filter chain. The SubjectFilter constructs a Shiro Subject instance reflecting the broker client and ensures the Subject instance is available for all downstream broker filters that may need to use the Subject to perform security operations.

The SubjectFilter is mostly a ‘behind the scenes’ component of the SubjectFilter, but it does offer some customization for advanced use cases:

  • the ability to customize exactly how broker clients’ Subject instances are created via a ConnectionSubjectFactory and
  • the ability to customize how the ActiveMQ Classic ConnectionContext’s SecurityContext is constructed.

Unless you’re deeply familiar with ActiveMQ Classic’s security model, you can safely skip to Authentication below.

ConnectionSubjectFactory

A ConnectionSubjectFactory creates a Subject instance that represents the broker client’s identity. The SubjectFilter’s default instance is a DefaultConnectionSubjectFactory

Most ConnectionSubjectFactory implementations will simply use Shiro’s Subject.Builder to create an anonymous Subject instance and let the downstream AuthenticationFilter authenticate the Subject based on any credentials associated with the connection. After authentication, the Subject will have an identity, and this is the expected flow for most connection clients.

However, if there is some other data associated with the connection that can be inspected to create a Subject instance beyond what the DefaultConnectionSubjectFactory, you can implement the ConnectionSubjectFactory interface and plug it in to the SubjectFilter:

<bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
    <property name="subjectFilter.connectionSubjectFactory">
        <bean class="com.my.ConnectionSubjectFactory" .../>
    </property>
</bean>

SecurityContextFactory

The ActiveMQ Classic ConnectionContext associated with broker client connections utilizes a SecurityContext object. When the SubjectFilter executes, it needs to create a Shiro-specific SecurityContext and associate it with the ConnectionContext so the Subject may be accessed downstream for all subsequent security operations.

The SubjectFilter delegates SecurityContext creation to a SecurityContextFactory instance. The DefaultSecurityContextFactory implementation returns SubjectSecurityContext instances based on the connection’s associated Subject. It should be an extremely rare thing to change, but if you must configure a custom SecurityContextFactory, you can do as follows:

<bean id="shiroPlugin" class="org.apache.activemq.shiro.ShiroPlugin" xmlns="http://www.springframework.org/schema/beans">
    <property name="subjectFilter.securityContextFactory">
        <bean class="com.my.SecurityContextFactory" .../>
    </property>
</bean>

Note however that much of the plugin’s functionality and downstream filters expect created SecurityContext instances to be SubjectSecurityContext instances.

Authentication

The ShiroPlugin installs the AuthenticationFilter immediately after the SubjectFilter in the broker filter chain. The AuthenticationFilter ensures that the broker client Subject is authenticated if necessary before allowing the chain to continue. If authentication is required and the Subject is not authenticated, the broker filter chain will not be executed, ensuring only verified identities may perform further behavior.

WORK IN PROGRESS - STILL AUTHORING

Apache, ActiveMQ, Apache ActiveMQ, the Apache feather logo, and the Apache ActiveMQ project logo are trademarks of The Apache Software Foundation. Copyright © 2024, The Apache Software Foundation. Licensed under Apache License 2.0.