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:51 UTC

[4/4] git commit: 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!

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!

Commit dev changes to branch.

Signed-off-by: Christian Posta <ch...@gmail.com>

Added shiro support to 5.9.x

Signed-off-by: Christian Posta <ch...@gmail.com>

Added Shiro deps to dist zip/tarball and 'all' .jar

Signed-off-by: Christian Posta <ch...@gmail.com>

updated version number to reflect correctly packaging Shiro features

Signed-off-by: Christian Posta <ch...@gmail.com>

Handled case when adding a producer might not have a specified destination.

Signed-off-by: Christian Posta <ch...@gmail.com>

Merged latest Connection API changes from trunk.

Signed-off-by: Christian Posta <ch...@gmail.com>

removed accidental addition of Stormpath's m2 repo

Signed-off-by: Christian Posta <ch...@gmail.com>

reverted accidental deletion of <distributionManagement>

Signed-off-by: Christian Posta <ch...@gmail.com>


Project: http://git-wip-us.apache.org/repos/asf/activemq/repo
Commit: http://git-wip-us.apache.org/repos/asf/activemq/commit/f9451e56
Tree: http://git-wip-us.apache.org/repos/asf/activemq/tree/f9451e56
Diff: http://git-wip-us.apache.org/repos/asf/activemq/diff/f9451e56

Branch: refs/heads/trunk
Commit: f9451e56e20f8d1712ed09c579d493feee514898
Parents: e7e317d
Author: Les Hazlewood <le...@hazlewood.com>
Authored: Wed Dec 11 18:42:27 2013 -0800
Committer: Christian Posta <ch...@gmail.com>
Committed: Mon Feb 24 08:17:32 2014 -0700

----------------------------------------------------------------------
 activemq-all/pom.xml                            |   8 +
 activemq-shiro/pom.xml                          | 178 ++++++++
 .../activemq/shiro/ConnectionReference.java     |  66 +++
 .../shiro/DefaultSecurityContextFactory.java    |  45 +++
 .../activemq/shiro/SecurityContextFactory.java  |  56 +++
 .../apache/activemq/shiro/SecurityFilter.java   |  41 ++
 .../org/apache/activemq/shiro/ShiroPlugin.java  | 246 +++++++++++
 .../shiro/authc/AuthenticationFilter.java       | 137 +++++++
 .../shiro/authc/AuthenticationPolicy.java       |  58 +++
 .../shiro/authc/AuthenticationTokenFactory.java |  46 +++
 .../authc/DefaultAuthenticationPolicy.java      | 212 ++++++++++
 .../DefaultAuthenticationTokenFactory.java      |  59 +++
 .../org/apache/activemq/shiro/authz/Action.java |  35 ++
 .../shiro/authz/ActionPermissionResolver.java   |  50 +++
 .../shiro/authz/ActiveMQPermissionResolver.java |  57 +++
 .../shiro/authz/ActiveMQWildcardPermission.java | 275 +++++++++++++
 .../shiro/authz/AuthorizationFilter.java        | 226 +++++++++++
 .../activemq/shiro/authz/DestinationAction.java |  90 +++++
 .../DestinationActionPermissionResolver.java    | 272 +++++++++++++
 .../activemq/shiro/env/EnvironmentFilter.java   |  45 +++
 .../activemq/shiro/env/IniEnvironment.java      | 136 +++++++
 .../mgt/DefaultActiveMqSecurityManager.java     |  41 ++
 .../session/mgt/DisabledSessionManager.java     |  39 ++
 .../shiro/subject/ConnectionSubjectFactory.java |  48 +++
 .../subject/ConnectionSubjectResolver.java      |  67 +++
 .../DefaultConnectionSubjectFactory.java        |  52 +++
 .../subject/SubjectConnectionReference.java     |  46 +++
 .../activemq/shiro/subject/SubjectFilter.java   | 119 ++++++
 .../activemq/shiro/subject/SubjectResolver.java |  32 ++
 .../shiro/subject/SubjectSecurityContext.java   |  89 ++++
 .../activemq/shiro/ConnectionReferenceTest.java |  43 ++
 .../activemq/shiro/SecurityFilterTest.java      |  37 ++
 .../apache/activemq/shiro/ShiroPluginTest.java  | 403 +++++++++++++++++++
 .../shiro/authc/AuthenticationFilterTest.java   |  86 ++++
 .../authc/DefaultAuthenticationPolicyTest.java  | 339 ++++++++++++++++
 .../authz/ActiveMQPermissionResolverTest.java   |  53 +++
 .../authz/ActiveMQWildcardPermissionTest.java   | 158 ++++++++
 .../shiro/authz/AuthorizationFilterTest.java    | 364 +++++++++++++++++
 ...DestinationActionPermissionResolverTest.java | 153 +++++++
 .../shiro/authz/DestinationActionTest.java      |  58 +++
 .../shiro/env/EnvironmentFilterTest.java        |  30 ++
 .../activemq/shiro/env/IniEnvironmentTest.java  | 121 ++++++
 .../session/mgt/DisabledSessionManagerTest.java |  47 +++
 .../subject/ConnectionSubjectResolverTest.java  |  80 ++++
 .../DefaultConnectionSubjectFactoryTest.java    |  55 +++
 .../activemq/shiro/subject/SubjectAdapter.java  | 185 +++++++++
 .../subject/SubjectConnectionReferenceTest.java |  33 ++
 .../shiro/subject/SubjectFilterTest.java        |  59 +++
 .../subject/SubjectSecurityContextTest.java     |  58 +++
 .../src/test/resources/empty.shiro.ini          |  20 +
 .../src/test/resources/minimal.shiro.ini        |  63 +++
 .../src/test/resources/nosystem.shiro.ini       |  38 ++
 .../activemq/shiro/embedded-ini-config.xml      |  95 +++++
 .../activemq/shiro/external-ini-config.xml      |  53 +++
 .../org/apache/activemq/shiro/no-ini-config.xml |  78 ++++
 activemq-shiro/src/test/resources/shiro.ini     |  64 +++
 assembly/pom.xml                                |  12 +
 assembly/src/main/descriptors/common-bin.xml    |   7 +-
 pom.xml                                         |  36 ++
 59 files changed, 5698 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-all/pom.xml
----------------------------------------------------------------------
diff --git a/activemq-all/pom.xml b/activemq-all/pom.xml
index 3007874..1eb5430 100644
--- a/activemq-all/pom.xml
+++ b/activemq-all/pom.xml
@@ -106,6 +106,7 @@
                   <include>${project.groupId}:activemq-jaas</include>
                   <include>${project.groupId}:activemq-broker</include>
                   <include>${project.groupId}:activemq-console</include>
+                  <include>${project.groupId}:activemq-shiro</include>
                   <include>${project.groupId}:activemq-spring</include>
                   <include>${project.groupId}:activemq-pool</include>
                   <include>${project.groupId}:activemq-jms-pool</include>
@@ -231,6 +232,13 @@
             </dependency>
             <dependency>
               <groupId>${project.groupId}</groupId>
+              <artifactId>activemq-shiro</artifactId>
+              <version>${project.version}</version>
+              <classifier>sources</classifier>
+              <optional>true</optional>
+            </dependency>
+            <dependency>
+              <groupId>${project.groupId}</groupId>
               <artifactId>activemq-spring</artifactId>
               <version>${project.version}</version>
               <classifier>sources</classifier>

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/pom.xml
----------------------------------------------------------------------
diff --git a/activemq-shiro/pom.xml b/activemq-shiro/pom.xml
new file mode 100644
index 0000000..c273b9e
--- /dev/null
+++ b/activemq-shiro/pom.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.activemq</groupId>
+        <artifactId>activemq-parent</artifactId>
+        <version>5.10-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>activemq-shiro</artifactId>
+    <packaging>bundle</packaging>
+    <name>ActiveMQ :: Shiro</name>
+    <description>ActiveMQ secured by Apache Shiro</description>
+
+    <dependencies>
+
+        <!-- =============================== -->
+        <!-- Required Dependencies -->
+        <!-- =============================== -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>activemq-broker</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.shiro</groupId>
+            <artifactId>shiro-spring</artifactId>
+        </dependency>
+
+        <!-- =============================== -->
+        <!-- Optional Dependencies           -->
+        <!-- =============================== -->
+        <!-- <dependency>
+            <groupId>com.thoughtworks.xstream</groupId>
+            <artifactId>xstream</artifactId>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.codehaus.jettison</groupId>
+            <artifactId>jettison</artifactId>
+            <optional>true</optional>
+        </dependency> -->
+
+        <!-- =============================== -->
+        <!-- Testing Dependencies            -->
+        <!-- =============================== -->
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>activemq-kahadb-store</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>activemq-broker</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>activemq-unit-tests</artifactId>
+            <type>test-jar</type>
+            <scope>test</scope>
+        </dependency>
+        <!-- <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>activemq-jaas</artifactId>
+            <scope>test</scope>
+        </dependency> -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                    <instructions>
+                        <Bundle-SymbolicName>org.apache.activemq.shiro</Bundle-SymbolicName>
+                        <Export-Package>org.apache.activemq.shiro*;version=${project.version};-noimport:=true;-split-package:=merge-first</Export-Package>
+                        <Import-Package>
+                            org.apache.activemq*;version=${project.version};resolution:=optional,
+                            org.apache.shiro*;version="[1.2,2]",
+                            *
+                        </Import-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <!-- <build>
+        <resources>
+            <resource>
+                <directory>${project.basedir}/src/main/resources</directory>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+            <resource>
+                <directory>${project.basedir}/src/main/filtered-resources</directory>
+                <filtering>true</filtering>
+                <includes>
+                    <include>**/*</include>
+                </includes>
+            </resource>
+        </resources>
+
+        <plugins>
+            <plugin>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <configuration>
+                    <forkMode>always</forkMode>
+                    <argLine>${surefire.argLine}</argLine>
+                    <runOrder>alphabetical</runOrder>
+                    <failIfNoTests>false</failIfNoTests>
+                    <systemProperties>
+                        <property>
+                            <name>org.apache.activemq.default.directory.prefix</name>
+                            <value>target/</value>
+                        </property>
+                        <!- - Uncomment the following if you want to configure custom logging (using src/test/resources/log4j.properties)
+                             while running mvn:test
+                             Note: if you want to see log messages on the console window remove
+                                   "redirectTestOutputToFile" from the parent pom
+                        - ->
+                        <!- -
+                        <property>
+                          <name>log4j.configuration</name>
+                          <value>file:target/test-classes/log4j.properties</value>
+                        </property>
+                        - ->
+                    </systemProperties>
+                    <includes>
+                        <include>**/*Test.*</include>
+                    </includes>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build> -->
+</project>

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
new file mode 100644
index 0000000..06c277a
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ConnectionReference.java
@@ -0,0 +1,66 @@
+/**
+ * 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.Environment;
+
+/**
+ * A reference (handle) to a client's {@link ConnectionContext} and {@link ConnectionInfo} as well as the Shiro
+ * {@link Environment}.
+ * <p/>
+ * This implementation primarily exists as a <a href="http://sourcemaking.com/refactoring/introduce-parameter-object">
+ * Parameter Object Design Pattern</a> implementation to eliminate long parameter lists, but provides additional
+ * benefits, such as immutability and non-null guarantees, and possibility for future data without forcing method
+ * signature changes.
+ *
+ * @since 5.10.0
+ */
+public class ConnectionReference {
+
+    private final ConnectionContext connectionContext;
+    private final ConnectionInfo connectionInfo;
+    private final Environment environment;
+
+    public ConnectionReference(ConnectionContext connCtx, ConnectionInfo connInfo, Environment environment) {
+        if (connCtx == null) {
+            throw new IllegalArgumentException("ConnectionContext argument cannot be null.");
+        }
+        if (connInfo == null) {
+            throw new IllegalArgumentException("ConnectionInfo argument cannot be null.");
+        }
+        if (environment == null) {
+            throw new IllegalArgumentException("Environment argument cannot be null.");
+        }
+        this.connectionContext = connCtx;
+        this.connectionInfo = connInfo;
+        this.environment = environment;
+    }
+
+    public ConnectionContext getConnectionContext() {
+        return connectionContext;
+    }
+
+    public ConnectionInfo getConnectionInfo() {
+        return connectionInfo;
+    }
+
+    public Environment getEnvironment() {
+        return environment;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.java
new file mode 100644
index 0000000..2ac65bd
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/DefaultSecurityContextFactory.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;
+
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectSecurityContext;
+import org.apache.shiro.env.Environment;
+
+/**
+ * Default {@code SecurityContextFactory} implementation that creates
+ * {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instances, allowing the connection's {@code Subject} and the Shiro
+ * {@link Environment} to be available to downstream security broker filters.
+ *
+ * @since 5.10.0
+ */
+public class DefaultSecurityContextFactory implements SecurityContextFactory {
+
+    /**
+     * Returns a new {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instance, allowing the connection's {@code Subject} and the Shiro
+     * {@link Environment} to be available to downstream security broker filters.
+     *
+     * @param conn the subject's connection
+     * @return a new {@link org.apache.activemq.shiro.subject.SubjectSecurityContext} instance, allowing the connection's {@code Subject} and the Shiro
+     *         {@link Environment} to be available to downstream security broker filters.
+     */
+    @Override
+    public SecurityContext createSecurityContext(SubjectConnectionReference conn) {
+        return new SubjectSecurityContext(conn);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
new file mode 100644
index 0000000..044fb03
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityContextFactory.java
@@ -0,0 +1,56 @@
+/**
+ * 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.security.SecurityContext;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * A {@code SecurityContextFactory} returns a {@link SecurityContext} instance that retains a client
+ * connection's {@link Subject} instance.
+ * <p/>
+ * It should be noted that at the time a {@code SecurityContextFactory} is invoked, a {@link Subject} is already
+ * associated with the client connection.  A {@code SecurityContextFactory} is merely responsible for creating
+ * a Shiro-specific {@link org.apache.activemq.security.SecurityContext SecurityContext} instance.
+ * <p/>
+ * The returned {@code SecurityContext} instance will then be made available to any downstream Broker Filters via
+ * {@code connectionContext.}{@link org.apache.activemq.broker.ConnectionContext#getSecurityContext() getSecurityContext()}
+ * to ensure it may be used for Shiro-based security checks.
+ *
+ * @see org.apache.activemq.shiro.subject.SubjectSecurityContext
+ * @since 5.10.0
+ */
+public interface SecurityContextFactory {
+
+    /**
+     * Creates a new {@link SecurityContext} retaining the client connection's {@link Subject} instance.
+     * <p/>
+     * It should be noted that at the time a {@code SecurityContextFactory} is invoked, a {@code Subject} is already
+     * associated with the client connection.  A {@code SecurityContextFactory} is merely responsible for creating
+     * a Shiro-specific {@link org.apache.activemq.security.SecurityContext SecurityContext} instance.
+     * <p/>
+     * The returned {@code SecurityContext} instance will then be made available to any downstream Broker Filters via
+     * {@code connectionContext.}{@link org.apache.activemq.broker.ConnectionContext#getSecurityContext() getSecurityContext()}
+     * to ensure it may be used for Shiro-based security checks.
+     *
+     * @param ref the client's connection and subject
+     * @return a new {@link SecurityContext} retaining the client connection's {@link Subject} instance.
+     * @see org.apache.activemq.shiro.subject.SubjectSecurityContext
+     */
+    SecurityContext createSecurityContext(SubjectConnectionReference ref);
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.java
new file mode 100644
index 0000000..8fbd9de
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/SecurityFilter.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;
+
+import org.apache.activemq.broker.MutableBrokerFilter;
+
+/**
+ * @since 5.10.0
+ */
+public abstract class SecurityFilter extends MutableBrokerFilter {
+
+    private volatile boolean enabled;
+
+    public SecurityFilter() {
+        super(null);
+        this.enabled = true;
+    }
+
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
new file mode 100644
index 0000000..95e69b1
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/ShiroPlugin.java
@@ -0,0 +1,246 @@
+/**
+ * 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.ConfigurationException;
+import org.apache.activemq.broker.Broker;
+import org.apache.activemq.broker.BrokerPluginSupport;
+import org.apache.activemq.shiro.authc.AuthenticationFilter;
+import org.apache.activemq.shiro.authc.AuthenticationPolicy;
+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.ConnectionSubjectFactory;
+import org.apache.activemq.shiro.subject.DefaultConnectionSubjectFactory;
+import org.apache.activemq.shiro.subject.SubjectFilter;
+import org.apache.shiro.config.Ini;
+import org.apache.shiro.env.Environment;
+import org.apache.shiro.mgt.SecurityManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @since 5.10.0
+ */
+public class ShiroPlugin extends BrokerPluginSupport {
+
+    private static final Logger LOG = LoggerFactory.getLogger(ShiroPlugin.class);
+
+    private volatile boolean enabled = true;
+
+    private Broker broker; //the downstream broker after any/all Shiro-specific broker filters
+
+    private SecurityManager securityManager;
+    private Environment environment;
+    private IniEnvironment iniEnvironment; //only used if the above environment instance is not explicitly configured
+
+    private SubjectFilter subjectFilter;
+
+    private AuthenticationFilter authenticationFilter;
+
+    private AuthorizationFilter authorizationFilter;
+
+    public ShiroPlugin() {
+
+        //Default if this.environment is not configured. See the ensureEnvironment() method below.
+        iniEnvironment = new IniEnvironment();
+
+        authorizationFilter = new AuthorizationFilter();
+
+        // we want to share one AuthenticationPolicy instance across both the AuthenticationFilter and the
+        // ConnectionSubjectFactory:
+        AuthenticationPolicy authcPolicy = new DefaultAuthenticationPolicy();
+
+        authenticationFilter = new AuthenticationFilter();
+        authenticationFilter.setAuthenticationPolicy(authcPolicy);
+        authenticationFilter.setNext(authorizationFilter);
+
+        subjectFilter = new SubjectFilter();
+        DefaultConnectionSubjectFactory subjectFactory = new DefaultConnectionSubjectFactory();
+        subjectFactory.setAuthenticationPolicy(authcPolicy);
+        subjectFilter.setConnectionSubjectFactory(subjectFactory);
+        subjectFilter.setNext(authenticationFilter);
+    }
+
+    public SubjectFilter getSubjectFilter() {
+        return subjectFilter;
+    }
+
+    public void setSubjectFilter(SubjectFilter subjectFilter) {
+        this.subjectFilter = subjectFilter;
+        this.subjectFilter.setNext(this.authenticationFilter);
+    }
+
+    public AuthenticationFilter getAuthenticationFilter() {
+        return authenticationFilter;
+    }
+
+    public void setAuthenticationFilter(AuthenticationFilter authenticationFilter) {
+        this.authenticationFilter = authenticationFilter;
+        this.authenticationFilter.setNext(this.authorizationFilter);
+        this.subjectFilter.setNext(authenticationFilter);
+    }
+
+    public AuthorizationFilter getAuthorizationFilter() {
+        return authorizationFilter;
+    }
+
+    public void setAuthorizationFilter(AuthorizationFilter authorizationFilter) {
+        this.authorizationFilter = authorizationFilter;
+        this.authorizationFilter.setNext(this.broker);
+        this.authenticationFilter.setNext(authorizationFilter);
+    }
+
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+        if (isInstalled()) {
+            //we're running, so apply the changes now:
+            applyEnabled(enabled);
+        }
+    }
+
+    public boolean isEnabled() {
+        if (isInstalled()) {
+            return getNext() == this.subjectFilter;
+        }
+        return enabled;
+    }
+
+    private void applyEnabled(boolean enabled) {
+        if (enabled) {
+            //ensure the SubjectFilter and downstream filters are used:
+            super.setNext(this.subjectFilter);
+        } else {
+            //Shiro is not enabled, restore the original downstream broker:
+            super.setNext(this.broker);
+        }
+    }
+
+    public Environment getEnvironment() {
+        return environment;
+    }
+
+    public void setEnvironment(Environment environment) {
+        this.environment = environment;
+    }
+
+    public SecurityManager getSecurityManager() {
+        return securityManager;
+    }
+
+    public void setSecurityManager(SecurityManager securityManager) {
+        this.securityManager = securityManager;
+    }
+
+    public void setIni(Ini ini) {
+        this.iniEnvironment.setIni(ini);
+    }
+
+    public void setIniConfig(String iniConfig) {
+        this.iniEnvironment.setIniConfig(iniConfig);
+    }
+
+    public void setIniResourcePath(String resourcePath) {
+        this.iniEnvironment.setIniResourcePath(resourcePath);
+    }
+
+    // ===============================================================
+    // Authentication Configuration
+    // ===============================================================
+    public void setAuthenticationEnabled(boolean authenticationEnabled) {
+        this.authenticationFilter.setEnabled(authenticationEnabled);
+    }
+
+    public boolean isAuthenticationEnabled() {
+        return this.authenticationFilter.isEnabled();
+    }
+
+    public AuthenticationPolicy getAuthenticationPolicy() {
+        return authenticationFilter.getAuthenticationPolicy();
+    }
+
+    public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) {
+        authenticationFilter.setAuthenticationPolicy(authenticationPolicy);
+        //also set it on the ConnectionSubjectFactory:
+        ConnectionSubjectFactory factory = subjectFilter.getConnectionSubjectFactory();
+        if (factory instanceof DefaultConnectionSubjectFactory) {
+            ((DefaultConnectionSubjectFactory) factory).setAuthenticationPolicy(authenticationPolicy);
+        }
+    }
+
+    // ===============================================================
+    // Authorization Configuration
+    // ===============================================================
+    public void setAuthorizationEnabled(boolean authorizationEnabled) {
+        this.authorizationFilter.setEnabled(authorizationEnabled);
+    }
+
+    public boolean isAuthorizationEnabled() {
+        return this.authorizationFilter.isEnabled();
+    }
+
+    private Environment ensureEnvironment() throws ConfigurationException {
+        if (this.environment != null) {
+            return this.environment;
+        }
+
+        //this.environment is null - set it:
+        if (this.securityManager != null) {
+            this.environment = new Environment() {
+                @Override
+                public SecurityManager getSecurityManager() {
+                    return ShiroPlugin.this.securityManager;
+                }
+            };
+            return this.environment;
+        }
+
+        this.iniEnvironment.init(); //will automatically catch any config errors and throw.
+
+        this.environment = iniEnvironment;
+
+        return this.iniEnvironment;
+    }
+
+    @Override
+    public Broker installPlugin(Broker broker) throws Exception {
+
+        Environment environment = ensureEnvironment();
+
+        this.authorizationFilter.setEnvironment(environment);
+        this.authenticationFilter.setEnvironment(environment);
+        this.subjectFilter.setEnvironment(environment);
+
+        this.broker = broker;
+        this.authorizationFilter.setNext(broker);
+        this.authenticationFilter.setNext(this.authorizationFilter);
+        this.subjectFilter.setNext(this.authenticationFilter);
+
+        Broker next = this.subjectFilter;
+        if (!this.enabled) {
+            //not enabled at startup - default to the original broker:
+            next = broker;
+        }
+
+        setNext(next);
+        return this;
+    }
+
+    private boolean isInstalled() {
+        return getNext() != null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java
new file mode 100644
index 0000000..de68820
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationFilter.java
@@ -0,0 +1,137 @@
+/**
+ * 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.ConnectionContext;
+import org.apache.activemq.command.ConnectionInfo;
+import org.apache.activemq.security.SecurityContext;
+import org.apache.activemq.shiro.ConnectionReference;
+import org.apache.activemq.shiro.env.EnvironmentFilter;
+import org.apache.activemq.shiro.subject.ConnectionSubjectResolver;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectSecurityContext;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@code AuthenticationFilter} enforces if authentication is required before allowing the broker filter chain
+ * to continue.
+ * <p/>
+ * This implementation performs a connection-level authentication assertion:  If the {@link Subject} associated with the
+ * connection<b>*</b> is not authenticated, and the
+ * {@link AuthenticationPolicy AuthenticationPolicy} requires the {@code Subject} to be authenticated, it will attempt
+ * to {@link Subject#login(org.apache.shiro.authc.AuthenticationToken) login} the Subject automatically.  The
+ * {@link AuthenticationToken} used to login is created by the
+ * {@link #getAuthenticationTokenFactory() authenticationTokenFactory}, typically by acquiring any credentials
+ * associated with the connection.
+ * <p/>
+ * Once the connection's {@code Subject} is authenticated as necessary, the broker filter chain will continue
+ * as expected.
+ * <p/>
+ * <b>*</b>: The upstream {@link org.apache.activemq.shiro.subject.SubjectFilter} is expected to execute before this one, ensuring a Subject instance
+ * is already associated with the connection.
+ *
+ * @since 5.10.0
+ */
+public class AuthenticationFilter extends EnvironmentFilter {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);
+
+    private AuthenticationPolicy authenticationPolicy;
+    private AuthenticationTokenFactory authenticationTokenFactory;
+
+    public AuthenticationFilter() {
+        this.authenticationPolicy = new DefaultAuthenticationPolicy();
+        this.authenticationTokenFactory = new DefaultAuthenticationTokenFactory();
+    }
+
+    public AuthenticationPolicy getAuthenticationPolicy() {
+        return authenticationPolicy;
+    }
+
+    public void setAuthenticationPolicy(AuthenticationPolicy authenticationPolicy) {
+        this.authenticationPolicy = authenticationPolicy;
+    }
+
+    public AuthenticationTokenFactory getAuthenticationTokenFactory() {
+        return authenticationTokenFactory;
+    }
+
+    public void setAuthenticationTokenFactory(AuthenticationTokenFactory authenticationTokenFactory) {
+        this.authenticationTokenFactory = authenticationTokenFactory;
+    }
+
+    protected Subject getSubject(ConnectionReference conn) {
+        return new ConnectionSubjectResolver(conn).getSubject();
+    }
+
+    @Override
+    public void addConnection(ConnectionContext context, ConnectionInfo info) throws Exception {
+
+        if (isEnabled()) { //disabled means don't enforce authentication (i.e. allow anonymous access):
+
+            Subject subject = getSubject(new ConnectionReference(context, info, getEnvironment()));
+
+            if (!subject.isAuthenticated()) {
+
+                SubjectConnectionReference connection = new SubjectConnectionReference(context, info, getEnvironment(), subject);
+
+                if (this.authenticationPolicy.isAuthenticationRequired(connection)) {
+                    AuthenticationToken token = this.authenticationTokenFactory.getAuthenticationToken(connection);
+                    if (token == null) {
+                        String msg = "Unable to obtain authentication credentials for newly established connection.  " +
+                                "Authentication is required.";
+                        throw new AuthenticationException(msg);
+                    }
+                    //token is not null - login the current subject:
+                    subject.login(token);
+                }
+            }
+        }
+
+        super.addConnection(context, info);
+    }
+
+    @Override
+    public void removeConnection(ConnectionContext context, ConnectionInfo info, Throwable error) throws Exception {
+        try {
+            super.removeConnection(context, info, error);
+        } finally {
+            SecurityContext secCtx = context.getSecurityContext();
+
+            if (secCtx instanceof SubjectSecurityContext) {
+
+                SubjectSecurityContext subjectSecurityContext = (SubjectSecurityContext) secCtx;
+                Subject subject = subjectSecurityContext.getSubject();
+
+                if (subject != null) {
+                    try {
+                        subject.logout();
+                    } catch (Throwable t) {
+                        String msg = "Unable to cleanly logout connection Subject during connection removal.  This is " +
+                                "unexpected but not critical: it can be safely ignored because the " +
+                                "connection will no longer be used.";
+                        LOG.info(msg, t);
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
new file mode 100644
index 0000000..76c8464
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationPolicy.java
@@ -0,0 +1,58 @@
+/**
+ * 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.shiro.ConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.Subject;
+
+/**
+ * An {@code AuthenticationPolicy} customizes the behavior of the {@link AuthenticationFilter}, such as whether or not
+ * authentication is required or how to represent trusted/known {@code Subject} identities.
+ * <p/>
+ * Most will find customizing properties on the {@link DefaultAuthenticationPolicy} easier than implementing this
+ * interface directly.
+ *
+ * @see DefaultAuthenticationPolicy
+ * @since 5.10.0
+ */
+public interface AuthenticationPolicy {
+
+    /**
+     * Allows customization of the {@code Subject} being built for the specified client
+     * connection.  This allows for any pre-existing connection-specific identity or state to be applied to the
+     * {@link Subject.Builder} before the {@code Subject} instance is actually created.
+     * <p/>
+     * <b>NOTE:</b> This method is called by the {@link org.apache.activemq.shiro.subject.SubjectFilter SubjectFilter} <em>before</em> the filter chain
+     * is executed (and before an authentication attempt occurs).  Implementations <b><em>MUST NOT</em></b>
+     * attempt to actually {@link org.apache.shiro.subject.Subject.Builder#buildSubject() build} the subject or perform
+     * an authentication attempt in this method.
+     *
+     * @param subjectBuilder the builder for the Subject that will be created representing the associated client connection
+     * @param ref            a reference to the client's connection metadata
+     * @see org.apache.activemq.shiro.subject.SubjectFilter
+     */
+    void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference ref);
+
+    /**
+     * Returns {@code true} if the connection's {@code Subject} instance should be authenticated, {@code false} otherwise.
+     *
+     * @param ref the subject's connection
+     * @return {@code true} if the connection's {@code Subject} instance should be authenticated, {@code false} otherwise.
+     */
+    boolean isAuthenticationRequired(SubjectConnectionReference ref);
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.java
new file mode 100644
index 0000000..2134929
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/AuthenticationTokenFactory.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.authc;
+
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ * A {@code AuthenticationTokenFactory} inspects a newly-added ActiveMQ connection and returns a Shiro
+ * {@link AuthenticationToken} instance representing credentials associated with the connection.  These credentials can
+ * be used to {@link org.apache.shiro.subject.Subject#login(org.apache.shiro.authc.AuthenticationToken) authenticate}
+ * the connection, allowing for later identity and authorization (access control) checks.
+ *
+ * @see AuthenticationFilter#addConnection(org.apache.activemq.broker.ConnectionContext, org.apache.activemq.command.ConnectionInfo)
+ * @since 5.10.0
+ */
+public interface AuthenticationTokenFactory {
+
+    /**
+     * Returns a Shiro {@code AuthenticationToken} instance that should be used to authenticate the connection's
+     * {@link org.apache.shiro.subject.Subject}, or {@code null} if no authentication information can be obtained.
+     * <p/>
+     * If no {@code AuthenticationToken} can be obtained, the connection's Subject will be considered anonymous and any
+     * downstream security checks that enforce authentication or authorization will fail (as would be expected).
+     *
+     * @param ref the subject's connection
+     * @return a Shiro {@code AuthenticationToken} instance that should be used to authenticate the connection's
+     *         {@link org.apache.shiro.subject.Subject}, or {@code null} if no authentication information can be obtained.
+     * @throws Exception if there is a problem acquiring/creating an expected {@code AuthenticationToken}.
+     */
+    AuthenticationToken getAuthenticationToken(SubjectConnectionReference ref) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
new file mode 100644
index 0000000..654c5e5
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationPolicy.java
@@ -0,0 +1,212 @@
+/**
+ * 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.shiro.ConnectionReference;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.apache.shiro.subject.SimplePrincipalCollection;
+import org.apache.shiro.subject.Subject;
+
+import java.util.Collection;
+
+/**
+ * @since 5.10.0
+ */
+public class DefaultAuthenticationPolicy implements AuthenticationPolicy {
+
+    private boolean vmConnectionAuthenticationRequired = false;
+    private String systemAccountUsername = "system";
+    private String systemAccountRealmName = "iniRealm";
+
+    private boolean anonymousAccessAllowed = false;
+    private String anonymousAccountUsername = "anonymous";
+    private String anonymousAccountRealmName = "iniRealm";
+
+    public boolean isVmConnectionAuthenticationRequired() {
+        return vmConnectionAuthenticationRequired;
+    }
+
+    public void setVmConnectionAuthenticationRequired(boolean vmConnectionAuthenticationRequired) {
+        this.vmConnectionAuthenticationRequired = vmConnectionAuthenticationRequired;
+    }
+
+    public String getSystemAccountUsername() {
+        return systemAccountUsername;
+    }
+
+    public void setSystemAccountUsername(String systemAccountUsername) {
+        this.systemAccountUsername = systemAccountUsername;
+    }
+
+    public String getSystemAccountRealmName() {
+        return systemAccountRealmName;
+    }
+
+    public void setSystemAccountRealmName(String systemAccountRealmName) {
+        this.systemAccountRealmName = systemAccountRealmName;
+    }
+
+    public boolean isAnonymousAccessAllowed() {
+        return anonymousAccessAllowed;
+    }
+
+    public void setAnonymousAccessAllowed(boolean anonymousAccessAllowed) {
+        this.anonymousAccessAllowed = anonymousAccessAllowed;
+    }
+
+    public String getAnonymousAccountUsername() {
+        return anonymousAccountUsername;
+    }
+
+    public void setAnonymousAccountUsername(String anonymousAccountUsername) {
+        this.anonymousAccountUsername = anonymousAccountUsername;
+    }
+
+    public String getAnonymousAccountRealmName() {
+        return anonymousAccountRealmName;
+    }
+
+    public void setAnonymousAccountRealmName(String anonymousAccountRealmName) {
+        this.anonymousAccountRealmName = anonymousAccountRealmName;
+    }
+
+    /**
+     * Returns {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
+     * otherwise.
+     *
+     * @param conn the client's connection context
+     * @return {@code true} if the client connection has supplied credentials to authenticate itself, {@code false}
+     *         otherwise.
+     */
+    protected boolean credentialsAvailable(ConnectionReference conn) {
+        return conn.getConnectionInfo().getUserName() != null || conn.getConnectionInfo().getPassword() != null;
+    }
+
+    @Override
+    public boolean isAuthenticationRequired(SubjectConnectionReference conn) {
+        Subject subject = conn.getSubject();
+
+        if (subject.isAuthenticated()) {
+            //already authenticated:
+            return false;
+        }
+        //subject is not authenticated.  Authentication is required by default for all accounts other than
+        //the anonymous user (if enabled) or the vm account (if enabled)
+        if (isAnonymousAccessAllowed()) {
+            if (isAnonymousAccount(subject)) {
+                return false;
+            }
+        }
+
+        if (!isVmConnectionAuthenticationRequired()) {
+            if (isSystemAccount(subject)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected boolean isAnonymousAccount(Subject subject) {
+        PrincipalCollection pc = subject.getPrincipals();
+        return pc != null && matches(pc, anonymousAccountUsername, anonymousAccountRealmName);
+    }
+
+    protected boolean isSystemAccount(Subject subject) {
+        PrincipalCollection pc = subject.getPrincipals();
+        return pc != null && matches(pc, systemAccountUsername, systemAccountRealmName);
+    }
+
+    protected boolean matches(PrincipalCollection principals, String username, String realmName) {
+        Collection realmPrincipals = principals.fromRealm(realmName);
+        if (realmPrincipals != null && !realmPrincipals.isEmpty()) {
+            if (realmPrincipals.iterator().next().equals(username)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    protected boolean isSystemConnection(ConnectionReference conn) {
+        String remoteAddress = conn.getConnectionContext().getConnection().getRemoteAddress();
+        return remoteAddress.startsWith("vm:");
+    }
+
+    @Override
+    public void customizeSubject(Subject.Builder subjectBuilder, ConnectionReference conn) {
+        // We only need to specify a custom identity or authentication state if a normal authentication will not occur.
+        // If the client supplied connection credentials, the AuthenticationFilter will perform a normal authentication,
+        // so we should exit immediately:
+        if (credentialsAvailable(conn)) {
+            return;
+        }
+
+        //The connection cannot be authenticated, potentially implying a system or anonymous connection.  Check if so:
+        if (isAssumeIdentity(conn)) {
+            PrincipalCollection assumedIdentity = createAssumedIdentity(conn);
+            subjectBuilder.principals(assumedIdentity);
+        }
+    }
+
+    /**
+     * Returns {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
+     * otherwise.  This method will <em>only</em> be called if there are no connection
+     * {@link #credentialsAvailable(ConnectionReference) credentialsAvailable}.
+     * If a client supplies connection credentials, they will always be used to authenticate the client with that
+     * identity.
+     * <p/>
+     * If {@code true} is returned, the assumed identity will be returned by
+     * {@link #createAssumedIdentity(ConnectionReference) createAssumedIdentity}.
+     * <h3>Warning</h3>
+     * This method exists primarily to support the system and anonymous accounts - it is probably unsafe to return
+     * {@code true} in most other scenarios.
+     *
+     * @param conn a reference to the client's connection
+     * @return {@code true} if an unauthenticated connection should still assume a specific identity, {@code false}
+     *         otherwise.
+     */
+    protected boolean isAssumeIdentity(ConnectionReference conn) {
+        return isAnonymousAccessAllowed() ||
+                (isSystemConnection(conn) && !isVmConnectionAuthenticationRequired());
+    }
+
+    /**
+     * Returns a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
+     * the specified Connection.
+     * <p/>
+     * This method is <em>only</em> called if {@link #isAssumeIdentity(ConnectionReference)} is {@code true}.
+     *
+     * @param conn a reference to the client's connection
+     * @return a Shiro {@code PrincipalCollection} representing the identity to assume (without true authentication) for
+     *         the specified Connection.
+     */
+    protected PrincipalCollection createAssumedIdentity(ConnectionReference conn) {
+
+        //anonymous by default:
+        String username = anonymousAccountUsername;
+        String realmName = anonymousAccountRealmName;
+
+        //vm connections are special and should assume the system account:
+        if (isSystemConnection(conn)) {
+            username = systemAccountUsername;
+            realmName = systemAccountRealmName;
+        }
+
+        return new SimplePrincipalCollection(username, realmName);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
new file mode 100644
index 0000000..1c3a0d3
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authc/DefaultAuthenticationTokenFactory.java
@@ -0,0 +1,59 @@
+/**
+ * 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.command.ConnectionInfo;
+import org.apache.activemq.shiro.subject.SubjectConnectionReference;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+
+/**
+ * Default implementation of the {@link AuthenticationTokenFactory} interface that returns
+ * {@link org.apache.shiro.authc.UsernamePasswordToken UsernamePasswordToken} instances based on inspecting the
+ * {@link ConnectionInfo}.
+ *
+ * @since 5.10.0
+ */
+public class DefaultAuthenticationTokenFactory implements AuthenticationTokenFactory {
+
+    public DefaultAuthenticationTokenFactory() {
+    }
+
+    /**
+     * Returns a new {@link UsernamePasswordToken} instance populated based on the ConnectionInfo's
+     * {@link org.apache.activemq.command.ConnectionInfo#getUserName() userName} and
+     * {@link org.apache.activemq.command.ConnectionInfo#getPassword() password} properties.
+     *
+     * @param conn the subject's connection
+     * @return a new {@link UsernamePasswordToken} instance populated based on the ConnectionInfo's
+     *         ConnectionInfo's {@link org.apache.activemq.command.ConnectionInfo#getUserName() userName} and
+     *         {@link org.apache.activemq.command.ConnectionInfo#getPassword() password} properties.
+     */
+    @Override
+    public AuthenticationToken getAuthenticationToken(SubjectConnectionReference conn) {
+
+        String username = conn.getConnectionInfo().getUserName();
+        String password = conn.getConnectionInfo().getPassword();
+
+        if (username == null && password == null) {
+            //no identity or credentials provided by the client for the connection - return null to reflect this
+            return null;
+        }
+
+        return new UsernamePasswordToken(username, password);
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
new file mode 100644
index 0000000..dfde09f
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/Action.java
@@ -0,0 +1,35 @@
+/**
+ * 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;
+
+/**
+ * An {@code Action} represents an attempt to perform some behavior, typically on a particular resource.
+ *
+ * @see DestinationAction
+ * @since 5.10.0
+ */
+public interface Action {
+
+    /**
+     * Returns a human readable string that indicates what this action is, for example "open doors" or
+     * "delete file /usr/local/foo"
+     *
+     * @return a human readable string that indicates what this action is
+     */
+    String toString();
+
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
new file mode 100644
index 0000000..a379d21
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActionPermissionResolver.java
@@ -0,0 +1,50 @@
+/**
+ * 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.shiro.authz.Permission;
+
+import java.util.Collection;
+
+/**
+ * An {@code ActionPermissionResolver} will inspect an {@link Action} and return
+ * {@link Permission}s that must be granted to a {@link org.apache.shiro.subject.Subject Subject} in order for the
+ * {@code Subject} to execute the action.
+ * <p/>
+ * If a {@code Subject} is not granted all of the returned permissions, the {@code Action} will not be executed.
+ *
+ * @since 5.10.0
+ */
+public interface ActionPermissionResolver {
+
+    /**
+     * Returns all {@link Permission}s that must be granted to a
+     * {@link org.apache.shiro.subject.Subject Subject} in order for the {@code Subject} to execute the action, or
+     * an empty collection if no permissions are required.
+     * <p/>
+     * Most implementations will probably return a single Permission, but multiple permissions are possible, especially
+     * if the Action represents behavior attempted on a
+     * <a href="http://activemq.apache.org/composite-destinations.html">Composite Destination</a>.
+     *
+     * @param action the action attempted
+     * @return all {@link Permission}s that must be granted to a
+     *         {@link org.apache.shiro.subject.Subject Subject} in order for the {@code Subject} to execute the action,
+     *         or an empty collection if no permissions are required.
+     */
+    Collection<Permission> getPermissions(Action action);
+
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
new file mode 100644
index 0000000..620957b
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQPermissionResolver.java
@@ -0,0 +1,57 @@
+/**
+ * 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.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+import org.apache.shiro.authz.permission.WildcardPermissionResolver;
+
+/**
+ * {@link WildcardPermissionResolver} that can create case-sensitive (or case-insensitive)
+ * {@link WildcardPermission} instances as expected for ActiveMQ.
+ *
+ * @since 5.10.0
+ */
+public class ActiveMQPermissionResolver extends WildcardPermissionResolver {
+
+    private boolean caseSensitive;
+
+    public ActiveMQPermissionResolver() {
+        caseSensitive = true;
+    }
+
+    public boolean isCaseSensitive() {
+        return caseSensitive;
+    }
+
+    public void setCaseSensitive(boolean caseSensitive) {
+        this.caseSensitive = caseSensitive;
+    }
+
+    /**
+     * Creates a new {@link WildcardPermission} instance, with case-sensitivity determined by the
+     * {@link #isCaseSensitive() caseSensitive} setting.
+     *
+     * @param permissionString the wildcard permission-formatted string.
+     * @return a new {@link WildcardPermission} instance, with case-sensitivity determined by the
+     *         {@link #isCaseSensitive() caseSensitive} setting.
+     */
+    @Override
+    public Permission resolvePermission(String permissionString) {
+        return new ActiveMQWildcardPermission(permissionString, isCaseSensitive());
+    }
+}

http://git-wip-us.apache.org/repos/asf/activemq/blob/f9451e56/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
----------------------------------------------------------------------
diff --git a/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
new file mode 100644
index 0000000..38a514c
--- /dev/null
+++ b/activemq-shiro/src/main/java/org/apache/activemq/shiro/authz/ActiveMQWildcardPermission.java
@@ -0,0 +1,275 @@
+/**
+ * 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.shiro.authz.Permission;
+import org.apache.shiro.authz.permission.WildcardPermission;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @since 5.10.0
+ */
+public class ActiveMQWildcardPermission extends WildcardPermission {
+
+    private final boolean caseSensitive;
+
+    public ActiveMQWildcardPermission(String wildcardString) {
+        this(wildcardString, true);
+    }
+
+    public ActiveMQWildcardPermission(String wildcardString, boolean caseSensitive) {
+        super(wildcardString, caseSensitive);
+        this.caseSensitive = caseSensitive;
+    }
+
+    @Override
+    public boolean implies(Permission p) {
+        // By default only supports comparisons with other WildcardPermissions
+        if (!(p instanceof WildcardPermission)) {
+            return false;
+        }
+
+        WildcardPermission wp = (WildcardPermission) p;
+
+        List<Set<String>> otherParts = getParts(wp);
+
+        int i = 0;
+        for (Set<String> otherPart : otherParts) {
+            // If this permission has less parts than the other permission, everything after the number of parts contained
+            // in this permission is automatically implied, so return true
+            if (getParts().size() - 1 < i) {
+                return true;
+            } else {
+                Set<String> thisPart = getParts().get(i);
+
+                for (String token : thisPart) {
+                    if (token.equals(WILDCARD_TOKEN)) {
+                        continue;
+                    }
+                    for (String otherToken : otherPart) {
+                        if (!caseSensitive) {
+                            otherToken = otherToken.toLowerCase();
+                        }
+                        if (!matches(token, otherToken)) {
+                            return false;
+                        }
+                    }
+                }
+                i++;
+            }
+        }
+
+        // If this permission has more parts than the other parts, only imply it if all of the other parts are wildcards
+        for (; i < getParts().size(); i++) {
+            Set<String> part = getParts().get(i);
+            if (!part.contains(WILDCARD_TOKEN)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Tests whether or not a string matches against a pattern.
+     * The pattern may contain two special characters:<br>
+     * '*' means zero or more characters<br>
+     * '?' means one and only one character
+     *
+     * @param pattern pattern to match against.
+     *                Must not be <code>null</code>.
+     * @param value   string which must be matched against the pattern.
+     *                Must not be <code>null</code>.
+     * @return <code>true</code> if the string matches against the
+     *         pattern, or <code>false</code> otherwise.
+     */
+    protected boolean matches(String pattern, String value) {
+
+        char[] patArr = pattern.toCharArray();
+        char[] valArr = value.toCharArray();
+        int patIndex = 0;
+        int patEndIndex = patArr.length - 1;
+        int valIndex = 0;
+        int valEndIndex = valArr.length - 1;
+        char ch;
+
+        boolean patternContainsStar = false;
+        for (char patternChar : patArr) {
+            if (patternChar == '*') {
+                patternContainsStar = true;
+                break;
+            }
+        }
+
+        if (!patternContainsStar) {
+            // No '*'s, so we make a shortcut
+            if (patEndIndex != valEndIndex) {
+                return false; // Pattern and string do not have the same size
+            }
+            for (int i = 0; i <= patEndIndex; i++) {
+                ch = patArr[i];
+                if (ch != '?') {
+                    if (ch != valArr[i]) {
+                        return false;// Character mismatch
+                    }
+                }
+            }
+            return true; // String matches against pattern
+        }
+
+
+        // Process characters before first star
+        while ((ch = patArr[patIndex]) != '*' && valIndex <= valEndIndex) {
+            if (ch != '?') {
+                if (ch != valArr[valIndex]) {
+                    return false;// Character mismatch
+                }
+            }
+            patIndex++;
+            valIndex++;
+        }
+        if (valIndex > valEndIndex) {
+            // All characters in the value are used. Check if only '*'s remain
+            // in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIndex; i <= patEndIndex; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // Process characters after last star
+        while ((ch = patArr[patEndIndex]) != '*' && valIndex <= valEndIndex) {
+            if (ch != '?') {
+                if (ch != valArr[valEndIndex]) {
+                    return false;// Character mismatch
+                }
+            }
+            patEndIndex--;
+            valEndIndex--;
+        }
+        if (valIndex > valEndIndex) {
+            // All characters in the value are used. Check if only '*'s remain
+            // in the pattern. If so, we succeeded. Otherwise failure.
+            for (int i = patIndex; i <= patEndIndex; i++) {
+                if (patArr[i] != '*') {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // process pattern between stars. patIndex and patEndIndex always point to a '*'.
+        while (patIndex != patEndIndex && valIndex <= valEndIndex) {
+            int innerPatternIndex = -1;
+            for (int i = patIndex + 1; i <= patEndIndex; i++) {
+                if (patArr[i] == '*') {
+                    innerPatternIndex = i;
+                    break;
+                }
+            }
+            if (innerPatternIndex == patIndex + 1) {
+                // Two stars next to each other, skip the first one.
+                patIndex++;
+                continue;
+            }
+            // Find the pattern between patIndex & innerPatternIndex in the value between
+            // valIndex and valEndIndex
+            int innerPatternLength = (innerPatternIndex - patIndex - 1);
+            int innerValueLength = (valEndIndex - valIndex + 1);
+            int foundIndex = -1;
+            innerValueLoop:
+            for (int i = 0; i <= innerValueLength - innerPatternLength; i++) {
+                for (int j = 0; j < innerPatternLength; j++) {
+                    ch = patArr[patIndex + j + 1];
+                    if (ch != '?') {
+                        if (ch != valArr[valIndex + i + j]) {
+                            continue innerValueLoop;
+                        }
+                    }
+                }
+
+                foundIndex = valIndex + i;
+                break;
+            }
+
+            if (foundIndex == -1) {
+                return false;
+            }
+
+            patIndex = innerPatternIndex;
+            valIndex = foundIndex + innerPatternLength;
+        }
+
+        // All characters in the string are used. Check if only '*'s are left
+        // in the pattern. If so, we succeeded. Otherwise failure.
+        for (int i = patIndex; i <= patEndIndex; i++) {
+            if (patArr[i] != '*') {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    protected List<Set<String>> getParts(WildcardPermission wp) {
+        if (wp instanceof ActiveMQWildcardPermission) {
+            return ((ActiveMQWildcardPermission) wp).getParts();
+        } else {
+            return getPartsByReflection(wp);
+        }
+    }
+
+    protected List<Set<String>> getPartsByReflection(WildcardPermission wp) {
+        try {
+            return doGetPartsByReflection(wp);
+        } catch (Exception e) {
+            String msg = "Unable to obtain WildcardPermission instance's 'parts' value.";
+            throw new IllegalStateException(msg, e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    protected List<Set<String>> doGetPartsByReflection(WildcardPermission wp) throws Exception {
+        Method getParts = WildcardPermission.class.getDeclaredMethod("getParts");
+        getParts.setAccessible(true);
+        return (List<Set<String>>) getParts.invoke(wp);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buffer = new StringBuilder();
+        for (Set<String> part : getParts()) {
+            if (buffer.length() > 0) {
+                buffer.append(":");
+            }
+            boolean first = true;
+            for (String token : part) {
+                if (!first) {
+                    buffer.append(",");
+                }
+                buffer.append(token);
+                first = false;
+            }
+        }
+        return buffer.toString();
+    }
+}