You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by ce...@apache.org on 2014/02/24 16:30:50 UTC

[3/4] Fix for https://issues.apache.org/jira/browse/AMQ-3621 - Integrate Apache Shiro with ActiveMQ as "security solution" - many thanks to Les Hazlewood (lhazlewood) for the patch!

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/AuthorizationFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/AuthorizationFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/AuthorizationFilter.java
new file mode 100644
index 0000000..0c147d9
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/AuthorizationFilter.java
@@ -0,0 +1,226 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.authz;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.broker.ProducerBrokerExchange;
+import org.apache.activemq.broker.region.Destination;
+import org.apache.activemq.broker.region.Subscription;
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.command.ConsumerInfo;
+import org.apache.activemq.command.DestinationInfo;
+import org.apache.activemq.command.Message;
+import org.apache.activemq.command.ProducerInfo;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.env.EnvironmentFilter;
+import org.apache.activemq.shiro.subject.ConnectionSubjectResolver;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.UnauthorizedException;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.Subject;
+
+import java.util.Collection;
+
+/**
+ * The {@code AuthorizationFilter} asserts that actions are allowed to execute first before they are actually
+ * executed.  Such actions include creating, removing, reading from and writing to destinations.
+ * <p/>
+ * This implementation is strictly permission-based, allowing for the finest-grained security policies possible.
+ * Whenever a {@link Subject} associated with a connection attempts to perform an {@link org.apache.activemq.shiro.authz.Action} (such as creating a
+ * destination, or reading from a queue, etc), one or more {@link Permission}s representing that {@code action} are
+ * checked.
+ * <p/>
+ * If the {@code Subject}{@link Subject#isPermitted(org.apache.shiro.authz.Permission) isPermitted} to perform the
+ * {@code action}, the action is allowed to execute and the broker filter chain executes uninterrupted.
+ * <p/>
+ * However, if the {@code Subject} is not permitted to perform the action, an {@link UnauthorizedException} will be
+ * thrown, preventing the filter chain from executing that action.
+ * <h2>ActionPermissionResolver</h2>
+ * The attempted {@code Action} is guarded by one or more {@link Permission}s as indicated by a configurable
+ * {@link #setActionPermissionResolver(org.apache.activemq.shiro.authz.ActionPermissionResolver) actionPermissionResolver}.  The
+ * {@code actionPermissionResolver} indicates which permissions must be granted to the connection {@code Subject} in
+ * order for the action to execute.
+ * <p/>
+ * The default {@code actionPermissionResolver} instance is a
+ * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver DestinationActionPermissionResolver}, which indicates which permissions
+ * are required to perform any action on a particular destination.  Those familiar with Shiro's
+ * {@link org.apache.shiro.authz.permission.WildcardPermission WildcardPermission} syntax will find the
+ * {@code DestinationActionPermissionResolver}'s
+ * {@link org.apache.activemq.shiro.authz.DestinationActionPermissionResolver#createPermissionString createPermissionString} method
+ * documentation valuable for understanding how destination actions are represented as permissions.
+ *
+ * @see org.apache.activemq.shiro.authz.ActionPermissionResolver
+ * @see org.apache.activemq.shiro.authz.DestinationActionPermissionResolver
+ * @since 5.10.0
+ */
+public class AuthorizationFilter extends EnvironmentFilter {
+
+    private ActionPermissionResolver actionPermissionResolver;
+
+    public AuthorizationFilter() {
+        this.actionPermissionResolver = new DestinationActionPermissionResolver();
+    }
+
+    /**
+     * Returns the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to
+     * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a
+     * destination, or reading from a queue, etc).  The default instance is a
+     * {@link DestinationActionPermissionResolver}.
+     *
+     * @return the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to
+     *         a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a
+     *         destination, or reading from a queue, etc).
+     */
+    public ActionPermissionResolver getActionPermissionResolver() {
+        return actionPermissionResolver;
+    }
+
+    /**
+     * Sets the {@code ActionPermissionResolver} used to indicate which permissions are required to be granted to
+     * a {@link Subject} to perform a particular destination {@link org.apache.activemq.shiro.authz.Action}, (such as creating a
+     * destination, or reading from a queue, etc).  Unless overridden by this method, the default instance is a
+     * {@link DestinationActionPermissionResolver}.
+     *
+     * @param actionPermissionResolver the {@code ActionPermissionResolver} used to indicate which permissions are
+     *                                 required to be granted to a {@link Subject} to perform a particular destination
+     *                                 {@link org.apache.activemq.shiro.authz.Action}, (such as creating a destination, or reading from a queue, etc).
+     */
+    public void setActionPermissionResolver(ActionPermissionResolver actionPermissionResolver) {
+        this.actionPermissionResolver = actionPermissionResolver;
+    }
+
+    /**
+     * Returns the {@code Subject} associated with the specified connection using a
+     * {@link org.apache.activemq.shiro.subject.ConnectionSubjectResolver}.
+     *
+     * @param ctx the connection context
+     * @return the {@code Subject} associated with the specified connection.
+     */
+    protected Subject getSubject(ConnectionContext ctx) {
+        return new ConnectionSubjectResolver(ctx).getSubject();
+    }
+
+    protected String toString(Subject subject) {
+        PrincipalCollection pc = subject.getPrincipals();
+        if (pc != null && !pc.isEmpty()) {
+            return "[" + pc.toString() + "] ";
+        }
+        return "";
+    }
+
+    protected void assertAuthorized(DestinationAction action) {
+        assertAuthorized(action, action.getVerb());
+    }
+
+    //ActiveMQ internals will create a ConnectionContext with a SecurityContext that is not
+    //Shiro specific.  We need to allow actions for internal system operations:
+    protected boolean isSystemBroker(DestinationAction action) {
+        ConnectionContext context = action.getConnectionContext();
+        SecurityContext securityContext = context.getSecurityContext();
+        return securityContext != null && securityContext.isBrokerContext();
+    }
+
+    protected void assertAuthorized(DestinationAction action, String verbText) {
+        if (!isEnabled() || isSystemBroker(action)) {
+            return;
+        }
+
+        final Subject subject = getSubject(action.getConnectionContext());
+
+        Collection<Permission> perms = this.actionPermissionResolver.getPermissions(action);
+
+        if (!subject.isPermittedAll(perms)) {
+            String msg = createUnauthorizedMessage(subject, action, verbText);
+            throw new UnauthorizedException(msg);
+        }
+    }
+
+    protected String createUnauthorizedMessage(Subject subject, DestinationAction action, String verbDisplayText) {
+        return "Subject " + toString(subject) + "is not authorized to " + verbDisplayText + " destination: " + action.getDestination();
+    }
+
+    @Override
+    public void addDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
+
+        DestinationAction action = new DestinationAction(context, info.getDestination(), "create");
+        assertAuthorized(action);
+
+        super.addDestinationInfo(context, info);
+    }
+
+    @Override
+    public Destination addDestination(ConnectionContext context, ActiveMQDestination destination, boolean create) throws Exception {
+
+        DestinationAction action = new DestinationAction(context, destination, "create");
+        assertAuthorized(action);
+
+        return super.addDestination(context, destination, create);
+    }
+
+    @Override
+    public void removeDestination(ConnectionContext context, ActiveMQDestination destination, long timeout) throws Exception {
+
+        DestinationAction action = new DestinationAction(context, destination, "remove");
+        assertAuthorized(action);
+
+        super.removeDestination(context, destination, timeout);
+    }
+
+    @Override
+    public void removeDestinationInfo(ConnectionContext context, DestinationInfo info) throws Exception {
+
+        DestinationAction action = new DestinationAction(context, info.getDestination(), "remove");
+        assertAuthorized(action);
+
+        super.removeDestinationInfo(context, info);
+    }
+
+    @Override
+    public Subscription addConsumer(ConnectionContext context, ConsumerInfo info) throws Exception {
+
+        //Unlike when adding a producer, consumers must specify the destination at creation time, so we can rely on
+        //a destination being available to perform the authz check:
+        DestinationAction action = new DestinationAction(context, info.getDestination(), "read");
+        assertAuthorized(action, "read from");
+
+        return super.addConsumer(context, info);
+    }
+
+    @Override
+    public void addProducer(ConnectionContext context, ProducerInfo info) throws Exception {
+
+        // JMS allows producers to be created without first specifying a destination.  In these cases, every send
+        // operation must specify a destination.  Because of this, we only authorize 'addProducer' if a destination is
+        // specified. If not specified, the authz check in the 'send' method below will ensure authorization.
+        if (info.getDestination() != null) {
+            DestinationAction action = new DestinationAction(context, info.getDestination(), "write");
+            assertAuthorized(action, "write to");
+        }
+
+        super.addProducer(context, info);
+    }
+
+    @Override
+    public void send(ProducerBrokerExchange exchange, Message message) throws Exception {
+
+        DestinationAction action = new DestinationAction(exchange.getConnectionContext(), message.getDestination(), "write");
+        assertAuthorized(action, "write to");
+
+        super.send(exchange, message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationAction.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationAction.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationAction.java
new file mode 100644
index 0000000..07b6b48
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationAction.java
@@ -0,0 +1,90 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.authz;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ActiveMQDestination;
+
+/**
+ * A {@code DestinationAction} represents behavior being taken on a particular {@link ActiveMQDestination}, such as
+ * creation, removal, and reading messages from it or writing messages to it.  The exact behavior being taken on the
+ * specific {@link #getDestination() destination} is represented as a {@link #getVerb() verb} property, which is one of
+ * the following string tokens:
+ * <table>
+ * <tr>
+ * <th>Verb</th>
+ * <th>Description</th>
+ * </tr>
+ * <tr>
+ * <td>{@code create}</td>
+ * <td>Create a specific destination.</td>
+ * </tr>
+ * <tr>
+ * <td>{@code remove}</td>
+ * <td>Remove a specific destination.</td>
+ * </tr>
+ * <tr>
+ * <td>{@code read}</td>
+ * <td>Read (consume) messages from a specific destination.</td>
+ * </tr>
+ * <tr>
+ * <td>{@code write}</td>
+ * <td>Write messages to a specific destination.</td>
+ * </tr>
+ * </table>
+ *
+ * @since 5.10.0
+ */
+public class DestinationAction implements Action {
+
+    private final ConnectionContext connectionContext;
+    private final ActiveMQDestination destination;
+    private final String verb;
+
+    public DestinationAction(ConnectionContext connectionContext, ActiveMQDestination destination, String verb) {
+        if (connectionContext == null) {
+            throw new IllegalArgumentException("ConnectionContext argument cannot be null.");
+        }
+        if (destination == null) {
+            throw new IllegalArgumentException("ActiveMQDestination argument cannot be null.");
+        }
+        if (verb == null) {
+            throw new IllegalArgumentException("verb argument cannot be null.");
+        }
+
+        this.connectionContext = connectionContext;
+        this.destination = destination;
+        this.verb = verb;
+    }
+
+    public ConnectionContext getConnectionContext() {
+        return connectionContext;
+    }
+
+    public ActiveMQDestination getDestination() {
+        return destination;
+    }
+
+    public String getVerb() {
+        return verb;
+    }
+
+    @Override
+    public String toString() {
+        return this.verb + " destination: " + destination;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationActionPermissionResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationActionPermissionResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationActionPermissionResolver.java
new file mode 100644
index 0000000..0947529
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/DestinationActionPermissionResolver.java
@@ -0,0 +1,272 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.authz;
+
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A {@code DestinationActionPermissionResolver} inspects {@link DestinationAction}s and returns one or more
+ * {@link WildcardPermission}s that must be granted to a {@code Subject} in order for that {@code Subject} to
+ * perform the action being taken on an {@link ActiveMQDestination}.
+ * <p/>
+ * See the {@link #createPermissionString createPermissionString documentation} to see what the
+ * resulting {@link WildcardPermission} instances would look like.
+ *
+ * @see #createPermissionString(org.apache.activemq.command.ActiveMQDestination, String) )
+ * @see #setPermissionStringPrefix(String)
+ * @since 5.10.0
+ */
+public class DestinationActionPermissionResolver implements ActionPermissionResolver {
+
+    private String permissionStringPrefix;
+    private boolean permissionStringCaseSensitive = true;
+
+    /**
+     * Returns the String prefix that should be automatically prepended to a permission String before the
+     * String is converted to a {@link WildcardPermission} instance.  This is convenient if you want to provide a
+     * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any
+     * others you might assign to user accounts.  The default value is {@code null}, indicating no prefix will be
+     * set by default.
+     * <p/>
+     * For example, the default settings might result in permissions Strings that look like this:
+     * <pre>
+     * topic:TEST:create
+     * temp-queue:MyQueue:remove
+     * topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     * <p/>
+     * However, if your application has any application-specific permissions that start with the tokens {@code topic},
+     * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between
+     * application-specific permissions and those specific to ActiveMQ.  In this case you might set the
+     * {@code permissionStringPrefix}. For example, if you set:
+     * {@code resolver.setPermissionStringPrefix(&quot;jms&quot;);}, the above permission strings would look like this:
+     * <pre>
+     * jms:topic:TEST:create
+     * jms:temp-queue:MyQueue:remove
+     * jms:topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     * <p/>
+     * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}:
+     * <pre>
+     * activeMQ:topic:TEST:create
+     * activeMQ:temp-queue:MyQueue:remove
+     * activeMQ:topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     *
+     * @return any String prefix that should be automatically prepended to a permission String before the
+     *         String is converted to a {@link WildcardPermission} instance.  Useful for namespacing permissions.
+     */
+    public String getPermissionStringPrefix() {
+        return permissionStringPrefix;
+    }
+
+    /**
+     * Sets the String prefix that should be automatically prepended to a permission String before the
+     * String is converted to a {@link WildcardPermission} instance.  This is convenient if you want to provide a
+     * 'scope' or 'namespace' for ActiveMQ Destinations to clearly distinguish ActiveMQ-specific permissions from any
+     * others you might assign to user accounts. The default value is {@code null}, indicating no prefix will be
+     * set by default.
+     * <p/>
+     * For example, the default settings might result in permissions Strings that look like this:
+     * <pre>
+     * topic:TEST:create
+     * temp-queue:MyQueue:remove
+     * topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     * <p/>
+     * However, if your application has any application-specific permissions that start with the tokens {@code topic},
+     * {@code temp-topic}, {@code queue}, or {@code temp-queue}, you wouldn't be able to distinguish between
+     * application-specific permissions and those specific to ActiveMQ.  In this case you might set the
+     * {@code permissionStringPrefix}. For example, if you set:
+     * {@code resolver.setPermissionStringPrefix(&quot;jms&quot;);}, the above permission strings would look like this:
+     * <pre>
+     * jms:topic:TEST:create
+     * jms:temp-queue:MyQueue:remove
+     * jms:topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     * <p/>
+     * Similarly, if the {@code permissionStringPrefix} was equal to {@code activeMQ}:
+     * <pre>
+     * activeMQ:topic:TEST:create
+     * activeMQ:temp-queue:MyQueue:remove
+     * activeMQ:topic:ActiveMQ.Advisory.*:read
+     * </pre>
+     *
+     * @param permissionStringPrefix any String prefix that should be automatically prepended to a permission String
+     *                               before the String is converted to a {@link WildcardPermission} instance.  Useful
+     *                               for namespacing permissions.
+     */
+    public void setPermissionStringPrefix(String permissionStringPrefix) {
+        this.permissionStringPrefix = permissionStringPrefix;
+    }
+
+    /**
+     * Returns {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive,
+     * {@code false} otherwise.  The default value is {@code true}, which is <em>not</em> the normal
+     * {@link WildcardPermission} default setting.  This default was chosen to reflect ActiveMQ's
+     * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>.
+     *
+     * @return {@code true} if returned {@link WildcardPermission} instances should be considered case-sensitive,
+     *         {@code false} otherwise.
+     */
+    public boolean isPermissionStringCaseSensitive() {
+        return permissionStringCaseSensitive;
+    }
+
+    /**
+     * Sets whether returned {@link WildcardPermission} instances should be considered case-sensitive.
+     * The default value is {@code true}, which is <em>not</em> the normal
+     * {@link WildcardPermission} default setting.  This default was chosen to accurately reflect ActiveMQ's
+     * <a href="http://activemq.apache.org/are-destinations-case-sensitive.html">case-sensitive destination names</a>.
+     *
+     * @param permissionStringCaseSensitive whether returned {@link WildcardPermission} instances should be considered
+     *                                      case-sensitive.
+     */
+    public void setPermissionStringCaseSensitive(boolean permissionStringCaseSensitive) {
+        this.permissionStringCaseSensitive = permissionStringCaseSensitive;
+    }
+
+    @Override
+    public Collection<Permission> getPermissions(Action action) {
+        if (!(action instanceof DestinationAction)) {
+            throw new IllegalArgumentException("Action argument must be a " + DestinationAction.class.getName() + " instance.");
+        }
+        DestinationAction da = (DestinationAction) action;
+        return getPermissions(da);
+    }
+
+    protected Collection<Permission> getPermissions(DestinationAction da) {
+        ActiveMQDestination dest = da.getDestination();
+        String verb = da.getVerb();
+        return createPermissions(dest, verb);
+    }
+
+    protected Collection<Permission> createPermissions(ActiveMQDestination dest, String verb) {
+
+        Set<Permission> set;
+
+        if (dest.isComposite()) {
+            ActiveMQDestination[] composites = dest.getCompositeDestinations();
+            set = new LinkedHashSet<Permission>(composites.length);
+            for(ActiveMQDestination d : composites) {
+                Collection<Permission> perms = createPermissions(d, verb);
+                set.addAll(perms);
+            }
+        } else {
+            set = new HashSet<Permission>(1);
+            String permString = createPermissionString(dest, verb);
+            Permission perm = createPermission(permString);
+            set.add(perm);
+        }
+
+        return set;
+    }
+
+    /**
+     * Inspects the specified {@code destination} and {@code verb} and returns a {@link WildcardPermission}-compatible
+     * String the represents the action.
+     * <h3>Format</h3>
+     * This implementation returns WildcardPermission strings with the following format:
+     * <pre>
+     * optionalPermissionStringPrefix + destinationType + ':' + destinationPhysicalName + ':' + actionVerb
+     * </pre>
+     * where:
+     * <ol>
+     * <li>{@code optionalPermissionStringPrefix} is the {@link #getPermissionStringPrefix() permissionStringPrefix}
+     * followed by a colon delimiter (':').  This is only present if the {@code permissionStringPrefix} has been
+     * specified and is non-null</li>
+     * <li>{@code destinationType} is one of the following four string tokens:
+     * <ul>
+     * <li>{@code topic}</li>
+     * <li>{@code temp-topic}</li>
+     * <li>{@code queue}</li>
+     * <li>{@code temp-queue}</li>
+     * </ul>
+     * based on whether the {@link DestinationAction#getDestination() destination} is
+     * a topic, temporary topic, queue, or temporary queue (respectively).
+     * </li>
+     * <li>
+     * {@code destinationPhysicalName} is
+     * {@link org.apache.activemq.command.ActiveMQDestination#getPhysicalName() destination.getPhysicalName()}
+     * </li>
+     * <li>
+     * {@code actionVerb} is {@link DestinationAction#getVerb() action.getVerb()}
+     * </li>
+     * </ol>
+     * <h3>Examples</h3>
+     * With the default settings (no {@link #getPermissionStringPrefix() permissionStringPrefix}), this might produce
+     * strings that look like the following:
+     * <pre>
+     * topic:TEST:create
+     * temp-queue:MyTempQueue:remove
+     * queue:ActiveMQ.Advisory.*:read
+     * </pre>
+     * If {@link #getPermissionStringPrefix() permissionStringPrefix} was set to {@code jms}, the above examples would
+     * look like this:
+     * <pre>
+     * jms:topic:TEST:create
+     * jms:temp-queue:MyTempQueue:remove
+     * jms:queue:ActiveMQ.Advisory.*:read
+     * </pre>
+     *
+     * @param dest the destination to inspect and convert to a {@link WildcardPermission} string.
+     * @param verb the behavior taken on the destination
+     * @return a {@link WildcardPermission} string that represents the specified {@code action}.
+     * @see #getPermissionStringPrefix() getPermissionStringPrefix() for more on why you might want to set this value
+     */
+    protected String createPermissionString(ActiveMQDestination dest, String verb) {
+        if (dest.isComposite()) {
+            throw new IllegalArgumentException("Use createPermissionStrings for composite destinations.");
+        }
+
+        StringBuilder sb = new StringBuilder();
+
+        if (permissionStringPrefix != null) {
+            sb.append(permissionStringPrefix);
+            if (!permissionStringPrefix.endsWith(":")) {
+                sb.append(":");
+            }
+        }
+
+        if (dest.isTemporary()) {
+            sb.append("temp-");
+        }
+        if (dest.isTopic()) {
+            sb.append("topic:");
+        } else {
+            sb.append("queue:");
+        }
+
+        sb.append(dest.getPhysicalName());
+        sb.append(':');
+        sb.append(verb);
+
+        return sb.toString();
+
+    }
+
+    protected Permission createPermission(String permissionString) {
+        return new ActiveMQWildcardPermission(permissionString, isPermissionStringCaseSensitive());
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/EnvironmentFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/EnvironmentFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/EnvironmentFilter.java
new file mode 100644
index 0000000..085f376
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/EnvironmentFilter.java
@@ -0,0 +1,45 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.env;
+
+import org.apache.activemq.shiro.SecurityFilter;
+import org.apache.shiro.env.Environment;
+
+/**
+ * An abstract {@code BrokerFilter} that makes the Shiro {@link Environment} available to subclasses.
+ *
+ * @since 5.10.0
+ */
+public abstract class EnvironmentFilter extends SecurityFilter {
+
+    private Environment environment;
+
+    public EnvironmentFilter() {
+    }
+
+    public Environment getEnvironment() {
+        if (this.environment == null) {
+            String msg = "Environment has not yet been set.  This should be done before this broker filter is used.";
+            throw new IllegalStateException(msg);
+        }
+        return environment;
+    }
+
+    public void setEnvironment(Environment environment) {
+        this.environment = environment;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/IniEnvironment.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/IniEnvironment.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/IniEnvironment.java
new file mode 100644
index 0000000..9ca1980
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/env/IniEnvironment.java
@@ -0,0 +1,136 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.env;
+
+import org.apache.activemq.shiro.authz.ActiveMQPermissionResolver;
+import org.apache.activemq.shiro.mgt.DefaultActiveMqSecurityManager;
+import org.apache.shiro.ShiroException;
+import org.apache.shiro.config.ConfigurationException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.env.DefaultEnvironment;
+import org.apache.shiro.io.ResourceUtils;
+import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.realm.Realm;
+import org.apache.shiro.realm.text.IniRealm;
+import org.apache.shiro.util.Initializable;
+import org.apache.shiro.util.LifecycleUtils;
+
+import java.util.Map;
+
+/**
+ * @since 5.10.0
+ */
+public class IniEnvironment extends DefaultEnvironment implements Initializable {
+
+    private Ini ini;
+    private String iniConfig;
+    private String iniResourePath;
+
+    public IniEnvironment() {
+    }
+
+    public IniEnvironment(Ini ini) {
+        this.ini = ini;
+        init();
+    }
+
+    public IniEnvironment(String iniConfig) {
+        Ini ini = new Ini();
+        ini.load(iniConfig);
+        this.ini = ini;
+        init();
+    }
+
+    public void setIni(Ini ini) {
+        this.ini = ini;
+    }
+
+    public void setIniConfig(String config) {
+        this.iniConfig = config;
+    }
+
+    public void setIniResourcePath(String iniResourcePath) {
+        this.iniResourePath = iniResourcePath;
+    }
+
+    @Override
+    public void init() throws ShiroException {
+        //this.environment and this.securityManager are null.  Try Ini config:
+        Ini ini = this.ini;
+        if (ini != null) {
+            apply(ini);
+        }
+
+        if (this.objects.isEmpty() && this.iniConfig != null) {
+            ini = new Ini();
+            ini.load(this.iniConfig);
+            apply(ini);
+        }
+
+        if (this.objects.isEmpty() && this.iniResourePath != null) {
+            ini = new Ini();
+            ini.loadFromPath(this.iniResourePath);
+            apply(ini);
+        }
+
+        if (this.objects.isEmpty()) {
+            if (ResourceUtils.resourceExists("classpath:shiro.ini")) {
+                ini = new Ini();
+                ini.loadFromPath("classpath:shiro.ini");
+                apply(ini);
+            }
+        }
+
+        if (this.objects.isEmpty()) {
+            String msg = "Configuration error.  All heuristics for acquiring Shiro INI config " +
+                    "have been exhausted.  Ensure you configure one of the following properties: " +
+                    "1) ini 2) iniConfig 3) iniResourcePath and the Ini sections are not empty.";
+            throw new ConfigurationException(msg);
+        }
+
+        LifecycleUtils.init(this.objects.values());
+    }
+
+    protected void apply(Ini ini) {
+        if (ini != null && !ini.isEmpty()) {
+            Map<String, ?> objects = createObjects(ini);
+            this.ini = ini;
+            this.objects.clear();
+            this.objects.putAll(objects);
+        }
+    }
+
+    private Map<String, ?> createObjects(Ini ini) {
+        IniSecurityManagerFactory factory = new IniSecurityManagerFactory(ini) {
+
+            @Override
+            protected SecurityManager createDefaultInstance() {
+                return new DefaultActiveMqSecurityManager();
+            }
+
+            @Override
+            protected Realm createRealm(Ini ini) {
+                IniRealm realm = (IniRealm)super.createRealm(ini);
+                realm.setPermissionResolver(new ActiveMQPermissionResolver());
+                return realm;
+            }
+        };
+        factory.getInstance(); //trigger beans creation
+        return factory.getBeans();
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/mgt/DefaultActiveMqSecurityManager.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/mgt/DefaultActiveMqSecurityManager.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/mgt/DefaultActiveMqSecurityManager.java
new file mode 100644
index 0000000..e3d8e61
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/mgt/DefaultActiveMqSecurityManager.java
@@ -0,0 +1,41 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.mgt;
+
+import org.apache.activemq.shiro.session.mgt.DisabledSessionManager;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
+import org.apache.shiro.mgt.DefaultSubjectDAO;
+
+/**
+ * @since 5.10.0
+ */
+public class DefaultActiveMqSecurityManager extends DefaultSecurityManager {
+
+    public DefaultActiveMqSecurityManager() {
+        super();
+
+        //disable sessions entirely:
+        setSessionManager(new DisabledSessionManager());
+
+        //also prevent the SecurityManager impl from using the Session as a storage medium (i.e. after authc):
+        DefaultSubjectDAO subjectDao = (DefaultSubjectDAO)getSubjectDAO();
+        DefaultSessionStorageEvaluator sessionStorageEvaluator =
+                (DefaultSessionStorageEvaluator)subjectDao.getSessionStorageEvaluator();
+        sessionStorageEvaluator.setSessionStorageEnabled(false);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/session/mgt/DisabledSessionManager.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/session/mgt/DisabledSessionManager.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/session/mgt/DisabledSessionManager.java
new file mode 100644
index 0000000..5234b1b
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/session/mgt/DisabledSessionManager.java
@@ -0,0 +1,39 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.session.mgt;
+
+import org.apache.shiro.session.Session;
+import org.apache.shiro.session.SessionException;
+import org.apache.shiro.session.mgt.SessionContext;
+import org.apache.shiro.session.mgt.SessionKey;
+import org.apache.shiro.session.mgt.SessionManager;
+
+/**
+ * @since 5.10.0
+ */
+public class DisabledSessionManager implements SessionManager {
+
+    @Override
+    public Session start(SessionContext context) {
+        throw new UnsupportedOperationException("Sessions are disabled.");
+    }
+
+    @Override
+    public Session getSession(SessionKey key) throws SessionException {
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectFactory.java
new file mode 100644
index 0000000..322f616
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectFactory.java
@@ -0,0 +1,48 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A {@code ConnectionSubjectFactory} creates a {@code Subject} instance that represents the connection client's identity.
+ * <p/>
+ * Most implementations will simply use the {@link Subject.Builder Subject.Builder} to create an anonymous
+ * {@code Subject} instance and let a downstream {@link org.apache.activemq.shiro.authc.AuthenticationFilter} authenticate the {@code Subject} based on
+ * any credentials associated with the connection.  After authentication, the {@code Subject} will have an identity, and
+ * this is the expected flow for most connection clients.
+ * <p/>
+ * However, if there is some other data associated with the connection that can be inspected to create a
+ * {@code Subject} instance beyond what the {@link DefaultConnectionSubjectFactory} provides, this interface allows that
+ * logic to be plugged in as necessary.
+ *
+ * @see DefaultConnectionSubjectFactory
+ * @since 5.10.0
+ */
+public interface ConnectionSubjectFactory {
+
+    /**
+     * Creates a {@code Subject} instance representing the connection client.  It is common for {@code Subject} instances
+     * returned from this method to be anonymous until a downstream {@link org.apache.activemq.shiro.authc.AuthenticationFilter} authenticates the
+     * subject to associate an identity.
+     *
+     * @param ref a reference to the client's connection metadata
+     * @return a {@code Subject} instance representing the connection client.
+     */
+    Subject createSubject(ConnectionReference ref);
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectResolver.java
new file mode 100644
index 0000000..f71fb05
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/ConnectionSubjectResolver.java
@@ -0,0 +1,67 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A {@code SubjectResolver} that acquires the current Subject from a {@link org.apache.activemq.shiro.ConnectionReference}.
+ *
+ * @since 5.10.0
+ */
+public class ConnectionSubjectResolver implements SubjectResolver {
+
+    private final SubjectSecurityContext securityContext;
+
+    public ConnectionSubjectResolver(ConnectionContext connCtx) {
+        if (connCtx == null) {
+            throw new IllegalArgumentException("ConnectionContext argument cannot be null.");
+        }
+        SecurityContext secCtx = connCtx.getSecurityContext();
+        if (secCtx == null) {
+            String msg = "There is no SecurityContext available on the ConnectionContext.  It " +
+                    "is expected that a previous broker in the chain will create the SecurityContext prior to this " +
+                    "resolver being invoked.  Ensure you have configured the SubjectPlugin and that it is " +
+                    "configured before all other Shiro-dependent broker filters.";
+            throw new IllegalArgumentException(msg);
+        }
+        if (!(secCtx instanceof SubjectSecurityContext)) {
+            String msg = "The specified SecurityContext is expected to be a " + SubjectSecurityContext.class.getName() +
+                    " instance.  The current instance's class: " + secCtx.getClass().getName();
+            throw new IllegalArgumentException(msg);
+        }
+        this.securityContext = (SubjectSecurityContext) secCtx;
+    }
+
+    public ConnectionSubjectResolver(ConnectionReference conn) {
+        this(conn.getConnectionContext());
+    }
+
+    @Override
+    public Subject getSubject() {
+        Subject subject = securityContext.getSubject();
+        if (subject != null) {
+            return subject;
+        }
+        String msg = "There is no Subject available in the SecurityContext.  Ensure " +
+                "that the SubjectPlugin is configured before all other Shiro-dependent broker filters.";
+        throw new IllegalStateException(msg);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/DefaultConnectionSubjectFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/DefaultConnectionSubjectFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/DefaultConnectionSubjectFactory.java
new file mode 100644
index 0000000..853f701
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/DefaultConnectionSubjectFactory.java
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.authc.AuthenticationPolicy;
+import org.apache.activemq.shiro.authc.DefaultAuthenticationPolicy;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @since 5.10.0
+ */
+public class DefaultConnectionSubjectFactory implements ConnectionSubjectFactory {
+
+    private AuthenticationPolicy authenticationPolicy;
+
+    public DefaultConnectionSubjectFactory() {
+        this.authenticationPolicy = new DefaultAuthenticationPolicy();
+    }
+
+    public AuthenticationPolicy getAuthenticationPolicy() {
+        return authenticationPolicy;
+    }
+
+    public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) {
+        this.authenticationPolicy = authenticationPolicy;
+    }
+
+    @Override
+    public Subject createSubject(ConnectionReference conn) {
+
+        Subject.Builder builder = new Subject.Builder(conn.getEnvironment().getSecurityManager());
+
+        authenticationPolicy.customizeSubject(builder, conn);
+
+        return builder.buildSubject();
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectConnectionReference.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectConnectionReference.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectConnectionReference.java
new file mode 100644
index 0000000..dd415cb
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectConnectionReference.java
@@ -0,0 +1,46 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * {@link org.apache.activemq.shiro.ConnectionReference} that further provides access to the connection's Subject instance.
+ *
+ * @since 5.10.0
+ */
+public class SubjectConnectionReference extends ConnectionReference {
+
+    private final Subject subject;
+
+    public SubjectConnectionReference(ConnectionContext connCtx, ConnectionInfo connInfo,
+                                      Environment environment, Subject subject) {
+        super(connCtx, connInfo, environment);
+        if (subject == null) {
+            throw new IllegalArgumentException("Subject argument cannot be null.");
+        }
+        this.subject = subject;
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectFilter.java
new file mode 100644
index 0000000..962cd8e
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectFilter.java
@@ -0,0 +1,119 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.DefaultSecurityContextFactory;
+import org.apache.activemq.shiro.SecurityContextFactory;
+import org.apache.activemq.shiro.env.EnvironmentFilter;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * The {@code SubjectFilter} ensures a Shiro {@link Subject} representing the client's identity is associated with
+ * every connection to the ActiveMQ Broker.  The {@code Subject} is made available to downstream broker filters so
+ * they may perform security checks as necessary.
+ * <p/>
+ * This implementation does not perform any security checks/assertions itself.  It is expected that other broker filters
+ * will be configured after this one and those will perform any security behavior or checks as necessary.
+ *
+ * @since 5.10.0
+ */
+public class SubjectFilter extends EnvironmentFilter {
+
+    private ConnectionSubjectFactory connectionSubjectFactory;
+    private SecurityContextFactory securityContextFactory;
+
+    public SubjectFilter() {
+        this.connectionSubjectFactory = new DefaultConnectionSubjectFactory();
+        this.securityContextFactory = new DefaultSecurityContextFactory();
+    }
+
+    public ConnectionSubjectFactory getConnectionSubjectFactory() {
+        return connectionSubjectFactory;
+    }
+
+    public void setConnectionSubjectFactory(ConnectionSubjectFactory connectionSubjectFactory) {
+        if (connectionSubjectFactory == null) {
+            throw new IllegalArgumentException("ConnectionSubjectFactory argument cannot be null.");
+        }
+        this.connectionSubjectFactory = connectionSubjectFactory;
+    }
+
+    public SecurityContextFactory getSecurityContextFactory() {
+        return this.securityContextFactory;
+    }
+
+    public void setSecurityContextFactory(SecurityContextFactory securityContextFactory) {
+        if (securityContextFactory == null) {
+            throw new IllegalArgumentException("SecurityContextFactory argument cannot be null.");
+        }
+        this.securityContextFactory = securityContextFactory;
+    }
+
+    protected Subject createSubject(ConnectionReference conn) {
+        return this.connectionSubjectFactory.createSubject(conn);
+    }
+
+    protected SecurityContext createSecurityContext(SubjectConnectionReference conn) {
+        return this.securityContextFactory.createSecurityContext(conn);
+    }
+
+    /**
+     * Creates a {@link Subject} instance reflecting the specified Connection.  The {@code Subject} is then stored in
+     * a {@link SecurityContext} instance which is set as the Connection's
+     * {@link ConnectionContext#setSecurityContext(org.apache.activemq.security.SecurityContext) securityContext}.
+     *
+     * @param context state associated with the client's connection
+     * @param info    info about the client's connection
+     * @throws Exception if there is a problem creating a Subject or {@code SecurityContext} instance.
+     */
+    @Override
+    public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
+
+        if (isEnabled()) {
+
+            SecurityContext secCtx = context.getSecurityContext();
+
+            if (secCtx == null) {
+                ConnectionReference conn = new ConnectionReference(context, info, getEnvironment());
+                Subject subject = createSubject(conn);
+                SubjectConnectionReference subjectConn = new SubjectConnectionReference(context, info, getEnvironment(), subject);
+                secCtx = createSecurityContext(subjectConn);
+                context.setSecurityContext(secCtx);
+            }
+        }
+
+        try {
+            super.addConnection(context, info);
+        } catch (Exception e) {
+            context.setSecurityContext(null);
+            throw e;
+        }
+    }
+
+    @Override
+    public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+        try {
+            super.removeConnection(context, info, error);
+        } finally {
+            context.setSecurityContext(null);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectResolver.java
new file mode 100644
index 0000000..7273ff3
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectResolver.java
@@ -0,0 +1,32 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.shiro.subject.Subject;
+
+/**
+ * @since 5.10.0
+ */
+public interface SubjectResolver {
+
+    /**
+     * Resolves and returns a {@link Subject} instance.  If one cannot be found, a runtime {@code Exception} is thrown.
+     *
+     * @return a resolved {@code Subject} instance.
+     */
+    Subject getSubject() throws RuntimeException;
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectSecurityContext.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectSecurityContext.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectSecurityContext.java
new file mode 100644
index 0000000..1d752e2
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/subject/SubjectSecurityContext.java
@@ -0,0 +1,89 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.subject;
+
+import org.apache.activemq.command.ActiveMQDestination;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.shiro.subject.Subject;
+
+import java.security.Principal;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * ActiveMQ {@code SecurityContext} implementation that retains a Shiro {@code Subject} instance for use during
+ * security checks and other security-related operations.
+ *
+ * @since 5.10.0
+ */
+public class SubjectSecurityContext extends SecurityContext {
+
+    private final Subject subject;
+
+    public SubjectSecurityContext(SubjectConnectionReference conn) {
+        //The username might not be available at the time this object is instantiated (the Subject might be
+        //anonymous).  Instead we override the getUserName() method below and that will always delegate to the
+        //Subject to return the most accurate/freshest username available.
+        super(null);
+        this.subject = conn.getSubject();
+    }
+
+    public Subject getSubject() {
+        return subject;
+    }
+
+    private static String getUsername(Subject subject) {
+        if (subject != null) {
+            Object principal = subject.getPrincipal();
+            if (principal != null) {
+                return String.valueOf(principal);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getUserName() {
+        return getUsername(this.subject);
+    }
+
+    private static UnsupportedOperationException notAllowed(String methodName) {
+        String msg = "Do not invoke the '" + methodName + "' method or use a broker filter that invokes it.  Use one " +
+                "of the Shiro-based security filters instead.";
+        return new UnsupportedOperationException(msg);
+    }
+
+    @Override
+    public boolean isInOneOf(Set<?> allowedPrincipals) {
+        throw notAllowed("isInOneOf");
+    }
+
+    @Override
+    public ConcurrentHashMap<ActiveMQDestination, ActiveMQDestination> getAuthorizedReadDests() {
+        throw notAllowed("getAuthorizedReadDests");
+    }
+
+    @Override
+    public ConcurrentHashMap<ActiveMQDestination, ActiveMQDestination> getAuthorizedWriteDests() {
+        throw notAllowed("getAuthorizedWriteDests");
+    }
+
+    @Override
+    public Set<Principal> getPrincipals() {
+        throw notAllowed("getPrincipals");
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/test/java/org/apache/activemq/shiro/ConnectionReferenceTest.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/test/java/org/apache/activemq/shiro/ConnectionReferenceTest.java b/activemq-shiro/src/test/java/org/apache/activemq/shiro/ConnectionReferenceTest.java
new file mode 100644
index 0000000..9290afd
--- /dev/null
+++ b/activemq-shiro/src/test/java/org/apache/activemq/shiro/ConnectionReferenceTest.java
@@ -0,0 +1,43 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro;
+
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.shiro.env.DefaultEnvironment;
+import org.junit.Test;
+
+/**
+ * @since 5.10.0
+ */
+public class ConnectionReferenceTest {
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testNoConnectionContext() {
+        new ConnectionReference(null, new ConnectionInfo(), new DefaultEnvironment());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testNoConnectionInfo() {
+        new ConnectionReference(new ConnectionContext(), null, new DefaultEnvironment());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testNoEnvironment() {
+        new ConnectionReference(new ConnectionContext(), new ConnectionInfo(), null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/test/java/org/apache/activemq/shiro/SecurityFilterTest.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/test/java/org/apache/activemq/shiro/SecurityFilterTest.java b/activemq-shiro/src/test/java/org/apache/activemq/shiro/SecurityFilterTest.java
new file mode 100644
index 0000000..ca64205
--- /dev/null
+++ b/activemq-shiro/src/test/java/org/apache/activemq/shiro/SecurityFilterTest.java
@@ -0,0 +1,37 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 5.10.0
+ */
+public class SecurityFilterTest {
+
+    @Test
+    public void testEnabled() {
+
+        SecurityFilter filter = new SecurityFilter() {};
+        assertTrue(filter.isEnabled()); //enabled by default
+
+        filter.setEnabled(false);
+        assertFalse(filter.isEnabled());
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/test/java/org/apache/activemq/shiro/ShiroPluginTest.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/test/java/org/apache/activemq/shiro/ShiroPluginTest.java b/activemq-shiro/src/test/java/org/apache/activemq/shiro/ShiroPluginTest.java
new file mode 100644
index 0000000..67e9d61
--- /dev/null
+++ b/activemq-shiro/src/test/java/org/apache/activemq/shiro/ShiroPluginTest.java
@@ -0,0 +1,403 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro;
+
+import org.apache.activemq.broker.BrokerPlugin;
+import org.apache.activemq.broker.BrokerService;
+import org.apache.activemq.broker.MutableBrokerFilter;
+import org.apache.activemq.shiro.authc.AuthenticationFilter;
+import org.apache.activemq.shiro.authc.DefaultAuthenticationPolicy;
+import org.apache.activemq.shiro.authz.AuthorizationFilter;
+import org.apache.activemq.shiro.env.IniEnvironment;
+import org.apache.activemq.shiro.subject.DefaultConnectionSubjectFactory;
+import org.apache.activemq.shiro.subject.SubjectFilter;
+import org.apache.activemq.test.JmsResourceProvider;
+import org.apache.activemq.test.TestSupport;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.env.DefaultEnvironment;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.text.IniRealm;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * @since 5.10.0
+ */
+public class ShiroPluginTest extends TestSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ShiroPluginTest.class);
+
+    protected BrokerService broker;
+    protected SecureJmsResourceProvider resourceProvider;
+    protected ConnectionFactory connectionFactory;
+    protected Connection connection;
+    protected Session session;
+    protected Destination destination;
+    protected MessageConsumer consumer;
+    protected MessageProducer producer;
+
+    @Override
+    protected void setUp() throws Exception {
+        resourceProvider = new SecureJmsResourceProvider();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        LOG.info("Shutting down broker...");
+
+        if (session != null) {
+            session.close();
+        }
+        session = null;
+
+        if (connection != null) {
+            connection.close();
+        }
+        connection = null;
+
+        if (broker != null) {
+            broker.stop();
+            broker.waitUntilStopped();
+        }
+        broker = null;
+
+        LOG.info("Broker shut down.");
+    }
+
+    protected void reconnect() throws Exception {
+        reconnect(null, null);
+    }
+
+    protected void reconnect(String username, String password) throws Exception {
+        if (connection != null) {
+            // Close the prev connection.
+            connection.close();
+        }
+        session = null;
+        if (username == null && password == null) {
+            connection = resourceProvider.createConnection(connectionFactory);
+        } else {
+            connection = resourceProvider.createConnection(connectionFactory, username, password);
+        }
+        reconnectSession();
+        connection.start();
+    }
+
+    protected void reconnectSession() throws JMSException {
+        if (session != null) {
+            session.close();
+        }
+        session = resourceProvider.createSession(connection);
+        destination = resourceProvider.createDestination(session, getSubject());
+        producer = resourceProvider.createProducer(session, destination);
+        consumer = resourceProvider.createConsumer(session, destination);
+    }
+
+    protected ConnectionFactory newConnectionFactory() throws Exception {
+        return resourceProvider.createConnectionFactory();
+    }
+
+    protected void start() throws Exception {
+        startBroker();
+        topic = resourceProvider.isTopic();
+        connectionFactory = newConnectionFactory();
+    }
+
+    protected void startBroker() throws Exception {
+        broker.start();
+        broker.waitUntilStarted();
+    }
+
+    protected BrokerService createBroker(BrokerPlugin... plugins) throws Exception {
+        return createBroker(plugins, resourceProvider.getServerUri());
+    }
+
+    protected BrokerService createBroker(BrokerPlugin[] plugins, String... connectorUris) throws Exception {
+        BrokerService brokerService = new BrokerService();
+        if (plugins != null && plugins.length > 0) {
+            brokerService.setPlugins(plugins);
+        }
+        if (connectorUris != null) {
+            for (String uri : connectorUris) {
+                brokerService.addConnector(uri);
+            }
+        }
+        return brokerService;
+    }
+
+    protected ShiroPlugin createPlugin(String iniPath) {
+        Ini ini = Ini.fromResourcePath(iniPath);
+        Environment env = new IniEnvironment(ini);
+        ShiroPlugin plugin = new ShiroPlugin();
+        plugin.setEnvironment(env);
+        return plugin;
+    }
+
+    public void testNoEnvironmentOrSecurityManager() throws Exception {
+        //should build IniEnvironment from shiro.ini in the classpath at the least:
+        ShiroPlugin plugin = new ShiroPlugin();
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        Ini ini = Ini.fromResourcePath("classpath:shiro.ini");
+        IniRealm realm = (IniRealm) ((DefaultSecurityManager) plugin.getEnvironment().getSecurityManager()).getRealms().iterator().next();
+        assertEquals(ini, realm.getIni());
+    }
+
+    public void testSetIni() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+        Ini ini = Ini.fromResourcePath("classpath:minimal.shiro.ini");
+        plugin.setIni(ini);
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        IniRealm realm = (IniRealm) ((DefaultSecurityManager) plugin.getEnvironment().getSecurityManager()).getRealms().iterator().next();
+        assertSame(ini, realm.getIni());
+    }
+
+    public void testSetIniString() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+        plugin.setIniConfig(
+                "[users]\n" +
+                "system = manager, system\n" +
+                "[roles]\n" +
+                "system = *");
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        IniRealm realm = (IniRealm) ((DefaultSecurityManager) plugin.getEnvironment().getSecurityManager()).getRealms().iterator().next();
+        Ini ini = realm.getIni();
+        assertEquals(1, ini.getSection("users").size());
+        assertEquals("manager, system", ini.getSection("users").get("system"));
+        assertEquals(1, ini.getSection("roles").size());
+        assertEquals("*", ini.getSection("roles").get("system"));
+    }
+
+    public void testSetIniResourcePath() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+
+        String path = "classpath:minimal.shiro.ini";
+
+        plugin.setIniResourcePath(path);
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        Ini ini = Ini.fromResourcePath(path);
+
+        IniRealm realm = (IniRealm) ((DefaultSecurityManager) plugin.getEnvironment().getSecurityManager()).getRealms().iterator().next();
+        assertEquals(ini, realm.getIni());
+    }
+
+    public void testSetSubjectFilter() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        SubjectFilter filter = new SubjectFilter();
+        plugin.setSubjectFilter(filter);
+        assertSame(filter, plugin.getSubjectFilter());
+        //assert that the AuthenticationFilter is always the next filter in the chain after the SubjectFilter:
+        assertSame(plugin.getAuthenticationFilter(), filter.getNext());
+    }
+
+    public void testSetAuthenticationFilter() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        AuthenticationFilter filter = new AuthenticationFilter();
+        plugin.setAuthenticationFilter(filter);
+        assertSame(filter, plugin.getAuthenticationFilter());
+        //assert that the AuthenticationFilter is always the next filter in the chain after the SubjectFilter:
+        assertSame(plugin.getSubjectFilter().getNext(), filter);
+    }
+
+    public void testSetAuthorizationFilter() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        AuthorizationFilter filter = new AuthorizationFilter();
+        plugin.setAuthorizationFilter(filter);
+        assertSame(filter, plugin.getAuthorizationFilter());
+        //assert that the AuthenticationFilter is always the next filter in the chain after the AuthenticationFilter:
+        assertSame(plugin.getAuthenticationFilter().getNext(), filter);
+    }
+
+    public void testSetEnvironment() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        Environment env = new DefaultEnvironment();
+        plugin.setEnvironment(env);
+        assertSame(env, plugin.getEnvironment());
+    }
+
+    public void testSetSecurityManager() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        org.apache.shiro.mgt.SecurityManager securityManager = new DefaultSecurityManager();
+        plugin.setSecurityManager(securityManager);
+        assertSame(securityManager, plugin.getSecurityManager());
+    }
+
+    public void testSecurityManagerWhenInstalled() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+        org.apache.shiro.mgt.SecurityManager securityManager = new DefaultSecurityManager();
+        plugin.setSecurityManager(securityManager);
+
+        assertNull(plugin.getEnvironment()); //we will auto-create one when only a sm is provided
+
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        assertSame(securityManager, plugin.getSecurityManager());
+        assertNotNull(plugin.getEnvironment());
+        assertSame(securityManager, plugin.getEnvironment().getSecurityManager());
+    }
+
+    public void testEnabledWhenNotInstalled() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        assertTrue(plugin.isEnabled()); //enabled by default
+
+        plugin.setEnabled(false);
+        assertFalse(plugin.isEnabled());
+
+        plugin.setEnabled(true);
+        assertTrue(plugin.isEnabled());
+    }
+
+    public void testEnabledWhenInstalled() throws Exception {
+        ShiroPlugin plugin = createPlugin("classpath:minimal.shiro.ini");
+        this.broker = createBroker(plugin);
+        start();
+        assertTrue(plugin.isEnabled());
+
+        plugin.setEnabled(false);
+        assertFalse(plugin.isEnabled());
+
+        plugin.setEnabled(true);
+        assertTrue(plugin.isEnabled());
+    }
+
+    public void testAuthenticationEnabledWhenNotInstalled() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        assertTrue(plugin.isAuthenticationEnabled());
+
+        plugin.setAuthenticationEnabled(false);
+        assertFalse(plugin.isAuthenticationEnabled());
+
+        plugin.setAuthenticationEnabled(true);
+        assertTrue(plugin.isAuthenticationEnabled());
+    }
+
+    public void testAuthenticationEnabledWhenInstalled() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+        plugin.setEnvironment(new DefaultEnvironment());
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        assertTrue(plugin.isAuthenticationEnabled());
+
+        plugin.setAuthenticationEnabled(false);
+        assertFalse(plugin.isAuthenticationEnabled());
+
+        plugin.setAuthenticationEnabled(true);
+        assertTrue(plugin.isAuthenticationEnabled());
+    }
+
+    public void testSetAuthenticationPolicy() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        DefaultAuthenticationPolicy policy = new DefaultAuthenticationPolicy();
+        plugin.setAuthenticationPolicy(policy);
+        assertSame(policy, plugin.getAuthenticationPolicy());
+        assertSame(policy, plugin.getAuthenticationFilter().getAuthenticationPolicy());
+        assertSame(policy, ((DefaultConnectionSubjectFactory) plugin.getSubjectFilter().getConnectionSubjectFactory()).getAuthenticationPolicy());
+    }
+
+    public void testAuthorizationEnabledWhenNotInstalled() {
+        ShiroPlugin plugin = new ShiroPlugin();
+        assertTrue(plugin.isAuthorizationEnabled());
+
+        plugin.setAuthorizationEnabled(false);
+        assertFalse(plugin.isAuthorizationEnabled());
+
+        plugin.setAuthorizationEnabled(true);
+        assertTrue(plugin.isAuthorizationEnabled());
+    }
+
+    public void testAuthorizationEnabledWhenInstalled() throws Exception {
+        ShiroPlugin plugin = new ShiroPlugin();
+        plugin.setEnvironment(new DefaultEnvironment());
+        plugin.installPlugin(new MutableBrokerFilter(null));
+
+        assertTrue(plugin.isAuthorizationEnabled());
+
+        plugin.setAuthorizationEnabled(false);
+        assertFalse(plugin.isAuthorizationEnabled());
+
+        plugin.setAuthorizationEnabled(true);
+        assertTrue(plugin.isAuthorizationEnabled());
+    }
+
+
+    public void testSimple() throws Exception {
+        ShiroPlugin plugin = createPlugin("classpath:minimal.shiro.ini");
+        this.broker = createBroker(plugin);
+        start();
+        reconnect();
+    }
+
+    public void testDisabled() throws Exception {
+        ShiroPlugin plugin = createPlugin("classpath:nosystem.shiro.ini");
+        plugin.setEnabled(false);
+        this.broker = createBroker(plugin);
+        start();
+    }
+
+    public void testRuntimeDisableEnableChanges() throws Exception {
+        ShiroPlugin plugin = createPlugin("classpath:nosystem.shiro.ini");
+        ((DefaultAuthenticationPolicy) plugin.getAuthenticationPolicy()).setVmConnectionAuthenticationRequired(true);
+        plugin.setEnabled(false);
+        this.broker = createBroker(plugin);
+        start();
+
+        //connection has no credentials.  When disabled, this should succeed:
+        reconnect();
+
+        //now enable the plugin and assert that credentials are required:
+        plugin.setEnabled(true);
+
+        try {
+            reconnect();
+            fail("Connections without passwords in this configuration should fail.");
+        } catch (JMSException expected) {
+            assertTrue(expected.getCause() instanceof AuthenticationException);
+        }
+
+        //this should work now that we're authenticating:
+        reconnect("foo", "bar");
+    }
+
+    static class SecureJmsResourceProvider extends JmsResourceProvider {
+
+        /**
+         * Creates a connection, authenticating with the specified username and password.
+         *
+         * @see org.apache.activemq.test.JmsResourceProvider#createConnection(javax.jms.ConnectionFactory)
+         */
+        public Connection createConnection(ConnectionFactory cf, String username, String password) throws JMSException {
+            Connection connection = cf.createConnection(username, password);
+            if (getClientID() != null) {
+                connection.setClientID(getClientID());
+            }
+            return connection;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/test/java/org/apache/activemq/shiro/authc/AuthenticationFilterTest.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/test/java/org/apache/activemq/shiro/authc/AuthenticationFilterTest.java b/activemq-shiro/src/test/java/org/apache/activemq/shiro/authc/AuthenticationFilterTest.java
new file mode 100644
index 0000000..f2ded62
--- /dev/null
+++ b/activemq-shiro/src/test/java/org/apache/activemq/shiro/authc/AuthenticationFilterTest.java
@@ -0,0 +1,86 @@
+/**
+ * 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.
+ */
+package org.apache.activemq.shiro.authc;
+
+import org.apache.activemq.broker.Broker;
+import org.apache.activemq.broker.BrokerPluginSupport;
+import org.apache.activemq.broker.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.shiro.subject.SubjectAdapter;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectSecurityContext;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.env.DefaultEnvironment;
+import org.apache.shiro.subject.Subject;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @since 5.10.0
+ */
+public class AuthenticationFilterTest {
+
+    AuthenticationFilter filter = new AuthenticationFilter();
+
+    @Test
+    public void testSetAuthenticationTokenFactory() {
+        AuthenticationTokenFactory factory = new AuthenticationTokenFactory() {
+            @Override
+            public AuthenticationToken getAuthenticationToken(SubjectConnectionReference ref) throws Exception {
+                return null;
+            }
+        };
+        filter.setAuthenticationTokenFactory(factory);
+        assertSame(factory, filter.getAuthenticationTokenFactory());
+    }
+
+    @Test
+    public void testRemoveAuthenticationWithLogoutThrowable() throws Exception {
+
+        final boolean[] invoked = new boolean[1];
+
+        Broker broker = new BrokerPluginSupport() {
+            @Override
+            public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+                invoked[0] = true;
+            }
+        };
+
+        DefaultEnvironment env = new DefaultEnvironment();
+
+        filter.setNext(broker);
+        filter.setEnvironment(env);
+
+        Subject subject = new SubjectAdapter() {
+            @Override
+            public void logout() {
+                throw new RuntimeException("Simulated failure.");
+            }
+        };
+
+        ConnectionContext ctx = new ConnectionContext();
+        ConnectionInfo info = new ConnectionInfo();
+        SubjectConnectionReference conn = new SubjectConnectionReference(ctx, info, env, subject);
+        SubjectSecurityContext ssc = new SubjectSecurityContext(conn);
+        ctx.setSecurityContext(ssc);
+
+        filter.removeConnection(ctx, info, null);
+
+        assertTrue(invoked[0]);
+    }
+}