This is an automated email from the ASF dual-hosted git repository.

clebertsuconic pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/activemq-artemis.git


The following commit(s) were added to refs/heads/master by this push:
     new 9085340  ARTEMIS-2886 optimize security auth
     new 4e33b53  This closes #3246
9085340 is described below

commit 90853409a04bed9f64787447528defe869213b52
Author: Justin Bertram <[email protected]>
AuthorDate: Mon Aug 24 16:24:25 2020 +0200

    ARTEMIS-2886 optimize security auth
    
    Both authentication and authorization will hit the underlying security
    repository (e.g. files, LDAP, etc.). For example, creating a JMS
    connection and a consumer will result in 2 hits with the *same*
    authentication request. This can cause unwanted (and unnecessary)
    resource utilization, especially in the case of networked configuration
    like LDAP.
    
    There is already a rudimentary cache for authorization, but it is
    cleared *totally* every 10 seconds by default (controlled via the
    security-invalidation-interval setting), and it must be populated
    initially which still results in duplicate auth requests.
    
    This commit optimizes authentication and authorization via the following
    changes:
    
     - Replace our home-grown cache with Google Guava's cache. This provides
    simple caching with both time-based and size-based LRU eviction. See more
    at https://github.com/google/guava/wiki/CachesExplained. I also thought
    about using Caffeine, but we already have a dependency on Guava and the
    cache implementions look to be negligibly different for this use-case.
     - Add caching for authentication. Both successful and unsuccessful
    authentication attempts will be cached to spare the underlying security
    repository as much as possible. Authenticated Subjects will be cached
    and re-used whenever possible.
     - Authorization will used Subjects cached during authentication. If the
    required Subject is not in the cache it will be fetched from the
    underlying security repo.
     - Caching can be disabled by setting the security-invalidation-interval
    to 0.
     - Cache sizes are configurable.
     - Management operations exist to inspect cache sizes at runtime.
---
 .../org/apache/activemq/cli/test/ArtemisTest.java  |   2 +
 .../apache/activemq/artemis/logs/AuditLogger.java  |  16 ++
 .../api/config/ActiveMQDefaultConfiguration.java   |  20 +++
 .../api/core/management/ActiveMQServerControl.java |  12 ++
 artemis-features/src/main/resources/features.xml   |   1 +
 artemis-server/pom.xml                             |   8 +-
 .../artemis/core/config/Configuration.java         |  22 +++
 .../core/config/impl/ConfigurationImpl.java        |  26 +++
 .../deployers/impl/FileConfigurationParser.java    |   6 +-
 .../management/impl/ActiveMQServerControlImpl.java |  17 ++
 .../core/security/impl/SecurityStoreImpl.java      | 186 +++++++++++++++------
 .../core/server/impl/ActiveMQServerImpl.java       |   2 +-
 .../core/security/ActiveMQJAASSecurityManager.java |  48 +-----
 .../core/security/ActiveMQSecurityManager5.java    |  61 +++++++
 .../resources/schema/artemis-configuration.xsd     |  18 +-
 .../core/config/impl/FileConfigurationTest.java    |   2 +
 .../security/jaas/JAASSecurityManagerTest.java     |  13 +-
 .../resources/ConfigurationTest-full-config.xml    |   2 +
 .../ConfigurationTest-xinclude-config.xml          |   2 +
 docs/user-manual/en/security.md                    |  14 +-
 .../jms/example/JAASSecurityManagerWrapper.java    |  26 ++-
 .../management/ActiveMQServerControlTest.java      |  26 +++
 .../ActiveMQServerControlUsingCoreTest.java        |  10 ++
 .../tests/integration/security/SecurityTest.java   |  16 +-
 .../stomp/StompWithClientIdValidationTest.java     |   8 +-
 25 files changed, 440 insertions(+), 124 deletions(-)

diff --git 
a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java 
b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
index 92d4503..c050d2e 100644
--- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
+++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java
@@ -69,6 +69,7 @@ import 
org.apache.activemq.artemis.core.client.impl.ServerLocatorImpl;
 import org.apache.activemq.artemis.core.config.FileDeploymentManager;
 import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
 import org.apache.activemq.artemis.core.security.CheckType;
+import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.JournalType;
 import org.apache.activemq.artemis.core.server.management.ManagementContext;
@@ -621,6 +622,7 @@ public class ArtemisTest extends CliTestBase {
          activeMQServerControl.addSecuritySettings("myAddress", "myRole", 
"myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", "myRole", 
"myRole");
          // change properties files which should cause another "reload" event
          activeMQServerControl.addUser("foo", "bar", "myRole", true);
+         
((SecurityStoreImpl)activeMQServer.getSecurityStore()).invalidateAuthenticationCache();
          ClientSession session = sessionFactory.createSession("foo", "bar", 
false, false, false, false, 0);
          session.createQueue("myAddress", RoutingType.ANYCAST, "myQueue", 
true);
          ClientProducer producer = session.createProducer("myAddress");
diff --git 
a/artemis-commons/src/main/java/org/apache/activemq/artemis/logs/AuditLogger.java
 
b/artemis-commons/src/main/java/org/apache/activemq/artemis/logs/AuditLogger.java
index 7fcc0ed..11fd9d4 100644
--- 
a/artemis-commons/src/main/java/org/apache/activemq/artemis/logs/AuditLogger.java
+++ 
b/artemis-commons/src/main/java/org/apache/activemq/artemis/logs/AuditLogger.java
@@ -2737,4 +2737,20 @@ public interface AuditLogger extends BasicLogger {
    @LogMessage(level = Logger.Level.INFO)
    @Message(id = 601735, value = "User {0} is getting group rebalance pause 
dispatch property on target resource: {1} {2}", format = 
Message.Format.MESSAGE_FORMAT)
    void isGroupRebalancePauseDispatch(String user, Object source, Object... 
args);
+
+   static void getAuthenticationCacheSize(Object source) {
+      LOGGER.getAuthenticationCacheSize(getCaller(), source);
+   }
+
+   @LogMessage(level = Logger.Level.INFO)
+   @Message(id = 601736, value = "User {0} is getting authentication cache 
size on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
+   void getAuthenticationCacheSize(String user, Object source, Object... args);
+
+   static void getAuthorizationCacheSize(Object source) {
+      LOGGER.getAuthorizationCacheSize(getCaller(), source);
+   }
+
+   @LogMessage(level = Logger.Level.INFO)
+   @Message(id = 601737, value = "User {0} is getting authorization cache size 
on target resource: {1} {2}", format = Message.Format.MESSAGE_FORMAT)
+   void getAuthorizationCacheSize(String user, Object source, Object... args);
 }
diff --git 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
index f7d33ff..cffd263 100644
--- 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
+++ 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/config/ActiveMQDefaultConfiguration.java
@@ -160,6 +160,12 @@ public final class ActiveMQDefaultConfiguration {
    // how long (in ms) to wait before invalidating the security cache
    private static long DEFAULT_SECURITY_INVALIDATION_INTERVAL = 10000;
 
+   // how large to make the authentication cache
+   private static long DEFAULT_AUTHENTICATION_CACHE_SIZE = 1000;
+
+   // how large to make the authorization cache
+   private static long DEFAULT_AUTHORIZATION_CACHE_SIZE = 1000;
+
    // how long (in ms) to wait to acquire a file lock on the journal
    private static long DEFAULT_JOURNAL_LOCK_ACQUISITION_TIMEOUT = -1;
 
@@ -681,6 +687,20 @@ public final class ActiveMQDefaultConfiguration {
    }
 
    /**
+    * how large to make the authentication cache
+    */
+   public static long getDefaultAuthenticationCacheSize() {
+      return DEFAULT_AUTHENTICATION_CACHE_SIZE;
+   }
+
+   /**
+    * how large to make the authorization cache
+    */
+   public static long getDefaultAuthorizationCacheSize() {
+      return DEFAULT_AUTHORIZATION_CACHE_SIZE;
+   }
+
+   /**
     * how long (in ms) to wait to acquire a file lock on the journal
     */
    public static long getDefaultJournalLockAcquisitionTimeout() {
diff --git 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
index 84a70ad..ea739cd 100644
--- 
a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
+++ 
b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/management/ActiveMQServerControl.java
@@ -460,6 +460,18 @@ public interface ActiveMQServerControl {
    @Attribute(desc = ADDRESS_MEMORY_USAGE_PERCENTAGE_DESCRIPTION)
    int getAddressMemoryUsagePercentage();
 
+   /**
+    * Returns the runtime size of the authentication cache
+    */
+   @Attribute(desc = "The runtime size of the authentication cache")
+   long getAuthenticationCacheSize();
+
+   /**
+    * Returns the runtime size of the authorization cache
+    */
+   @Attribute(desc = "The runtime size of the authorization cache")
+   long getAuthorizationCacheSize();
+
    // Operations ----------------------------------------------------
    @Operation(desc = "Isolate the broker", impact = MBeanOperationInfo.ACTION)
    boolean freezeReplication();
diff --git a/artemis-features/src/main/resources/features.xml 
b/artemis-features/src/main/resources/features.xml
index 5d3c6a2..96aaa1a 100644
--- a/artemis-features/src/main/resources/features.xml
+++ b/artemis-features/src/main/resources/features.xml
@@ -71,6 +71,7 @@
                <bundle 
dependency="true">mvn:org.apache.commons/commons-text/${commons.text.version}</bundle>
                <bundle 
dependency="true">mvn:org.apache.commons/commons-lang3/${commons.lang.version}</bundle>
                <bundle 
dependency="true">mvn:org.jctools/jctools-core/${jctools.version}</bundle>
+               <bundle 
dependency="true">mvn:com.google.guava/guava/${guava.version}</bundle>
                <!-- Micrometer can't be included until it supports OSGi. It is 
currently an "optional" Maven dependency. -->
                <!--bundle 
dependency="true">mvn:io.micrometer/micrometer-core/${version.micrometer}</bundle-->
 
diff --git a/artemis-server/pom.xml b/artemis-server/pom.xml
index aa41cf2..378b30e 100644
--- a/artemis-server/pom.xml
+++ b/artemis-server/pom.xml
@@ -45,8 +45,12 @@
          <optional>true</optional>
       </dependency>
       <dependency>
-          <groupId>com.google.errorprone</groupId>
-          <artifactId>error_prone_core</artifactId>
+         <groupId>com.google.errorprone</groupId>
+         <artifactId>error_prone_core</artifactId>
+      </dependency>
+      <dependency>
+         <groupId>com.google.guava</groupId>
+         <artifactId>guava</artifactId>
       </dependency>
       <dependency>
          <groupId>org.jboss.logging</groupId>
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
index 67d72de..0a50775 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/Configuration.java
@@ -212,6 +212,28 @@ public interface Configuration {
    Configuration setSecurityInvalidationInterval(long interval);
 
    /**
+    * Sets the size of the authentication cache.
+    */
+   Configuration setAuthenticationCacheSize(long size);
+
+   /**
+    * Returns the configured size of the authentication cache. <br>
+    * Default value is {@link 
org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_AUTHENTICATION_CACHE_SIZE}.
+    */
+   long getAuthenticationCacheSize();
+
+   /**
+    * Sets the size of the authorization cache.
+    */
+   Configuration setAuthorizationCacheSize(long size);
+
+   /**
+    * Returns the configured size of the authorization cache. <br>
+    * Default value is {@link 
org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_AUTHORIZATION_CACHE_SIZE}.
+    */
+   long getAuthorizationCacheSize();
+
+   /**
     * Returns whether security is enabled for this server. <br>
     * Default value is {@link 
org.apache.activemq.artemis.api.config.ActiveMQDefaultConfiguration#DEFAULT_SECURITY_ENABLED}.
     */
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
index ca59995..10ab2ce 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java
@@ -122,6 +122,10 @@ public class ConfigurationImpl implements Configuration, 
Serializable {
 
    private long securityInvalidationInterval = 
ActiveMQDefaultConfiguration.getDefaultSecurityInvalidationInterval();
 
+   private long authenticationCacheSize = 
ActiveMQDefaultConfiguration.getDefaultAuthenticationCacheSize();
+
+   private long authorizationCacheSize = 
ActiveMQDefaultConfiguration.getDefaultAuthorizationCacheSize();
+
    private boolean securityEnabled = 
ActiveMQDefaultConfiguration.isDefaultSecurityEnabled();
 
    private boolean gracefulShutdownEnabled = 
ActiveMQDefaultConfiguration.isDefaultGracefulShutdownEnabled();
@@ -507,6 +511,28 @@ public class ConfigurationImpl implements Configuration, 
Serializable {
    }
 
    @Override
+   public long getAuthenticationCacheSize() {
+      return authenticationCacheSize;
+   }
+
+   @Override
+   public ConfigurationImpl setAuthenticationCacheSize(final long size) {
+      authenticationCacheSize = size;
+      return this;
+   }
+
+   @Override
+   public long getAuthorizationCacheSize() {
+      return authorizationCacheSize;
+   }
+
+   @Override
+   public ConfigurationImpl setAuthorizationCacheSize(final long size) {
+      authorizationCacheSize = size;
+      return this;
+   }
+
+   @Override
    public long getConnectionTTLOverride() {
       return connectionTTLOverride;
    }
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
index 95cae11..0952ff5 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/deployers/impl/FileConfigurationParser.java
@@ -370,7 +370,11 @@ public final class FileConfigurationParser extends 
XMLConfigurationUtil {
 
       config.setJMXUseBrokerName(getBoolean(e, "jmx-use-broker-name", 
config.isJMXUseBrokerName()));
 
-      config.setSecurityInvalidationInterval(getLong(e, 
"security-invalidation-interval", config.getSecurityInvalidationInterval(), 
Validators.GT_ZERO));
+      config.setSecurityInvalidationInterval(getLong(e, 
"security-invalidation-interval", config.getSecurityInvalidationInterval(), 
Validators.GE_ZERO));
+
+      config.setAuthenticationCacheSize(getLong(e, 
"authentication-cache-size", config.getAuthenticationCacheSize(), 
Validators.GE_ZERO));
+
+      config.setAuthorizationCacheSize(getLong(e, "authorization-cache-size", 
config.getAuthorizationCacheSize(), Validators.GE_ZERO));
 
       config.setConnectionTTLOverride(getLong(e, "connection-ttl-override", 
config.getConnectionTTLOverride(), Validators.MINUS_ONE_OR_GT_ZERO));
 
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
index e6493af..1dd4052 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/management/impl/ActiveMQServerControlImpl.java
@@ -91,6 +91,7 @@ import 
org.apache.activemq.artemis.core.postoffice.impl.LocalQueueBinding;
 import org.apache.activemq.artemis.core.remoting.server.RemotingService;
 import org.apache.activemq.artemis.core.security.CheckType;
 import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
 import org.apache.activemq.artemis.core.server.ActiveMQMessageBundle;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
@@ -758,6 +759,22 @@ public class ActiveMQServerControlImpl extends 
AbstractControl implements Active
    }
 
    @Override
+   public long getAuthenticationCacheSize() {
+      if (AuditLogger.isEnabled()) {
+         AuditLogger.getAuthenticationCacheSize(this.server);
+      }
+      return 
((SecurityStoreImpl)server.getSecurityStore()).getAuthenticationCacheSize();
+   }
+
+   @Override
+   public long getAuthorizationCacheSize() {
+      if (AuditLogger.isEnabled()) {
+         AuditLogger.getAuthorizationCacheSize(this.server);
+      }
+      return 
((SecurityStoreImpl)server.getSecurityStore()).getAuthorizationCacheSize();
+   }
+
+   @Override
    public boolean freezeReplication() {
       if (AuditLogger.isEnabled()) {
          AuditLogger.freezeReplication(this.server);
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
index a1699b5..95e3b62 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/security/impl/SecurityStoreImpl.java
@@ -16,11 +16,14 @@
  */
 package org.apache.activemq.artemis.core.security.impl;
 
+import javax.security.auth.Subject;
 import javax.security.cert.X509Certificate;
 import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
 
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.apache.activemq.artemis.api.core.Pair;
 import org.apache.activemq.artemis.api.core.SimpleString;
 import org.apache.activemq.artemis.api.core.management.CoreNotificationType;
 import org.apache.activemq.artemis.api.core.management.ManagementHelper;
@@ -41,6 +44,8 @@ import 
org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager2;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager3;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager5;
+import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
 import org.apache.activemq.artemis.utils.CompositeAddress;
 import org.apache.activemq.artemis.utils.collections.ConcurrentHashSet;
 import org.apache.activemq.artemis.utils.collections.TypedProperties;
@@ -57,11 +62,9 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
 
    private final ActiveMQSecurityManager securityManager;
 
-   private final ConcurrentMap<String, ConcurrentHashSet<SimpleString>> cache 
= new ConcurrentHashMap<>();
+   private final Cache<String, ConcurrentHashSet<SimpleString>> 
authorizationCache;
 
-   private final long invalidationInterval;
-
-   private volatile long lastCheck;
+   private final Cache<String, Pair<Boolean, Subject>> authenticationCache;
 
    private boolean securityEnabled;
 
@@ -82,14 +85,23 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
                             final boolean securityEnabled,
                             final String managementClusterUser,
                             final String managementClusterPassword,
-                            final NotificationService notificationService) {
+                            final NotificationService notificationService,
+                            final long authenticationCacheSize,
+                            final long authorizationCacheSize) {
       this.securityRepository = securityRepository;
       this.securityManager = securityManager;
-      this.invalidationInterval = invalidationInterval;
       this.securityEnabled = securityEnabled;
       this.managementClusterUser = managementClusterUser;
       this.managementClusterPassword = managementClusterPassword;
       this.notificationService = notificationService;
+      authenticationCache = CacheBuilder.newBuilder()
+                                        .maximumSize(authenticationCacheSize)
+                                        
.expireAfterWrite(invalidationInterval, TimeUnit.MILLISECONDS)
+                                        .build();
+      authorizationCache = CacheBuilder.newBuilder()
+                                       .maximumSize(authorizationCacheSize)
+                                       .expireAfterWrite(invalidationInterval, 
TimeUnit.MILLISECONDS)
+                                       .build();
       this.securityRepository.registerListener(this);
    }
 
@@ -142,23 +154,40 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
 
          String validatedUser = null;
          boolean userIsValid = false;
+         boolean check = true;
 
-         if (securityManager instanceof ActiveMQSecurityManager4) {
-            validatedUser = ((ActiveMQSecurityManager4) 
securityManager).validateUser(user, password, connection, securityDomain);
-         } else if (securityManager instanceof ActiveMQSecurityManager3) {
-            validatedUser = ((ActiveMQSecurityManager3) 
securityManager).validateUser(user, password, connection);
-         } else if (securityManager instanceof ActiveMQSecurityManager2) {
-            userIsValid = ((ActiveMQSecurityManager2) 
securityManager).validateUser(user, password, 
CertificateUtil.getCertsFromConnection(connection));
-         } else {
-            userIsValid = securityManager.validateUser(user, password);
+         Pair<Boolean, Subject> cacheEntry = 
authenticationCache.getIfPresent(createAuthenticationCacheKey(user, password, 
connection));
+         if (cacheEntry != null) {
+            if (!cacheEntry.getA()) {
+               // cached authentication failed previously so don't check again
+               check = false;
+            } else {
+               // cached authentication succeeded previously so don't check 
again
+               check = false;
+               userIsValid = true;
+               validatedUser = getUserFromSubject(cacheEntry.getB());
+            }
          }
 
-         if (!userIsValid && validatedUser == null) {
-            String certSubjectDN = "unavailable";
-            X509Certificate[] certs = 
CertificateUtil.getCertsFromConnection(connection);
-            if (certs != null && certs.length > 0 && certs[0] != null) {
-               certSubjectDN = certs[0].getSubjectDN().getName();
+         if (check) {
+            if (securityManager instanceof ActiveMQSecurityManager5) {
+               Subject subject = ((ActiveMQSecurityManager5) 
securityManager).authenticate(user, password, connection, securityDomain);
+               authenticationCache.put(createAuthenticationCacheKey(user, 
password, connection), new Pair<>(subject != null, subject));
+               validatedUser = getUserFromSubject(subject);
+            } else if (securityManager instanceof ActiveMQSecurityManager4) {
+               validatedUser = ((ActiveMQSecurityManager4) 
securityManager).validateUser(user, password, connection, securityDomain);
+            } else if (securityManager instanceof ActiveMQSecurityManager3) {
+               validatedUser = ((ActiveMQSecurityManager3) 
securityManager).validateUser(user, password, connection);
+            } else if (securityManager instanceof ActiveMQSecurityManager2) {
+               userIsValid = ((ActiveMQSecurityManager2) 
securityManager).validateUser(user, password, 
CertificateUtil.getCertsFromConnection(connection));
+            } else {
+               userIsValid = securityManager.validateUser(user, password);
             }
+         }
+
+         // authentication failed, send a notification & throw an exception
+         if (!userIsValid && validatedUser == null) {
+            String certSubjectDN = getCertSubjectDN(connection);
 
             if (notificationService != null) {
                TypedProperties props = new TypedProperties();
@@ -184,6 +213,15 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
       return null;
    }
 
+   public String getCertSubjectDN(RemotingConnection connection) {
+      String certSubjectDN = "unavailable";
+      X509Certificate[] certs = 
CertificateUtil.getCertsFromConnection(connection);
+      if (certs != null && certs.length > 0 && certs[0] != null) {
+         certSubjectDN = certs[0].getSubjectDN().getName();
+      }
+      return certSubjectDN;
+   }
+
    @Override
    public void check(final SimpleString address,
                      final CheckType checkType,
@@ -201,8 +239,8 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
             logger.trace("checking access permissions to " + address);
          }
 
-         String user = session.getUsername();
          // bypass permission checks for management cluster user
+         String user = session.getUsername();
          if (managementClusterUser.equals(user) && 
session.getPassword().equals(managementClusterPassword)) {
             return;
          }
@@ -223,20 +261,20 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
             }
          }
 
-         if (checkCached(isFullyQualified ? fqqn : address, user, checkType)) {
+         if (checkAuthorizationCache(isFullyQualified ? fqqn : address, user, 
checkType)) {
             return;
          }
 
-         final boolean validated;
-         if (securityManager instanceof ActiveMQSecurityManager4) {
-            final ActiveMQSecurityManager4 securityManager4 = 
(ActiveMQSecurityManager4) securityManager;
-            validated = securityManager4.validateUserAndRole(user, 
session.getPassword(), roles, checkType, saddress, 
session.getRemotingConnection(), session.getSecurityDomain()) != null;
+         final Boolean validated;
+         if (securityManager instanceof ActiveMQSecurityManager5) {
+            Subject subject = getSubjectForAuthorization(session, 
((ActiveMQSecurityManager5) securityManager));
+            validated = ((ActiveMQSecurityManager5) 
securityManager).authorize(subject, roles, checkType);
+         } else if (securityManager instanceof ActiveMQSecurityManager4) {
+            validated = ((ActiveMQSecurityManager4) 
securityManager).validateUserAndRole(user, session.getPassword(), roles, 
checkType, saddress, session.getRemotingConnection(), 
session.getSecurityDomain()) != null;
          } else if (securityManager instanceof ActiveMQSecurityManager3) {
-            final ActiveMQSecurityManager3 securityManager3 = 
(ActiveMQSecurityManager3) securityManager;
-            validated = securityManager3.validateUserAndRole(user, 
session.getPassword(), roles, checkType, saddress, 
session.getRemotingConnection()) != null;
+            validated = ((ActiveMQSecurityManager3) 
securityManager).validateUserAndRole(user, session.getPassword(), roles, 
checkType, saddress, session.getRemotingConnection()) != null;
          } else if (securityManager instanceof ActiveMQSecurityManager2) {
-            final ActiveMQSecurityManager2 securityManager2 = 
(ActiveMQSecurityManager2) securityManager;
-            validated = securityManager2.validateUserAndRole(user, 
session.getPassword(), roles, checkType, saddress, 
session.getRemotingConnection());
+            validated = ((ActiveMQSecurityManager2) 
securityManager).validateUserAndRole(user, session.getPassword(), roles, 
checkType, saddress, session.getRemotingConnection());
          } else {
             validated = securityManager.validateUserAndRole(user, 
session.getPassword(), roles, checkType);
          }
@@ -263,11 +301,16 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
             AuditLogger.securityFailure(ex);
             throw ex;
          }
+
          // if we get here we're granted, add to the cache
-         ConcurrentHashSet<SimpleString> set = new ConcurrentHashSet<>();
-         ConcurrentHashSet<SimpleString> act = cache.putIfAbsent(user + "." + 
checkType.name(), set);
+         ConcurrentHashSet<SimpleString> set;
+         String key = createAuthorizationCacheKey(user, checkType);
+         ConcurrentHashSet<SimpleString> act = 
authorizationCache.getIfPresent(key);
          if (act != null) {
             set = act;
+         } else {
+            set = new ConcurrentHashSet<>();
+            authorizationCache.put(key, set);
          }
          set.add(isFullyQualified ? fqqn : address);
       }
@@ -275,39 +318,78 @@ public class SecurityStoreImpl implements SecurityStore, 
HierarchicalRepositoryC
 
    @Override
    public void onChange() {
-      invalidateCache();
+      invalidateAuthorizationCache();
+      // we don't invalidate the authentication cache here because it's not 
necessary
    }
 
-   // Public --------------------------------------------------------
+   public static String getUserFromSubject(Subject subject) {
+      if (subject == null) {
+         return null;
+      }
 
-   // Protected -----------------------------------------------------
+      String validatedUser = "";
+      Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
 
-   // Package Private -----------------------------------------------
+      // should only ever be 1 UserPrincipal
+      for (UserPrincipal userPrincipal : users) {
+         validatedUser = userPrincipal.getName();
+      }
+      return validatedUser;
+   }
 
-   // Private -------------------------------------------------------
-   private void invalidateCache() {
-      cache.clear();
+   /**
+    * Get the cached Subject. If the Subject is not in the cache then 
authenticate again to retrieve it.
+    *
+    * @param auth contains the authentication data
+    * @param securityManager used to authenticate the user if the Subject is 
not in the cache
+    * @return the authenticated Subject with all associated role principals
+    */
+   private Subject getSubjectForAuthorization(SecurityAuth auth, 
ActiveMQSecurityManager5 securityManager) {
+      Pair<Boolean, Subject> cached = 
authenticationCache.getIfPresent(createAuthenticationCacheKey(auth.getUsername(),
 auth.getPassword(), auth.getRemotingConnection()));
+      /*
+       * We don't need to worry about the cached boolean being false as users 
always have to
+       * successfully authenticate before requesting authorization for 
anything.
+       */
+      if (cached == null) {
+         return securityManager.authenticate(auth.getUsername(), 
auth.getPassword(), auth.getRemotingConnection(), auth.getSecurityDomain());
+      }
+      return cached.getB();
    }
 
-   private boolean checkCached(final SimpleString dest, final String user, 
final CheckType checkType) {
-      long now = System.currentTimeMillis();
+   // public for testing purposes
+   public void invalidateAuthorizationCache() {
+      authorizationCache.invalidateAll();
+   }
 
-      boolean granted = false;
+   // public for testing purposes
+   public void invalidateAuthenticationCache() {
+      authenticationCache.invalidateAll();
+   }
 
-      if (now - lastCheck > invalidationInterval) {
-         invalidateCache();
+   public long getAuthenticationCacheSize() {
+      return authenticationCache.size();
+   }
 
-         lastCheck = now;
-      } else {
-         ConcurrentHashSet<SimpleString> act = cache.get(user + "." + 
checkType.name());
-         if (act != null) {
-            granted = act.contains(dest);
-         }
+   public long getAuthorizationCacheSize() {
+      return authorizationCache.size();
+   }
+
+   private boolean checkAuthorizationCache(final SimpleString dest, final 
String user, final CheckType checkType) {
+      boolean granted = false;
+
+      ConcurrentHashSet<SimpleString> act = 
authorizationCache.getIfPresent(createAuthorizationCacheKey(user, checkType));
+      if (act != null) {
+         granted = act.contains(dest);
       }
 
       return granted;
    }
 
-   // Inner class ---------------------------------------------------
+   private String createAuthenticationCacheKey(String username, String 
password, RemotingConnection connection) {
+      return username + password + getCertSubjectDN(connection);
+   }
 
+   private String createAuthorizationCacheKey(String user, CheckType 
checkType) {
+      return user + "." + checkType.name();
+   }
 }
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
index d6c606f..43a42e6 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java
@@ -2884,7 +2884,7 @@ public class ActiveMQServerImpl implements ActiveMQServer 
{
          ActiveMQServerLogger.LOGGER.clusterSecurityRisk();
       }
 
-      securityStore = new SecurityStoreImpl(securityRepository, 
securityManager, configuration.getSecurityInvalidationInterval(), 
configuration.isSecurityEnabled(), configuration.getClusterUser(), 
configuration.getClusterPassword(), managementService);
+      securityStore = new SecurityStoreImpl(securityRepository, 
securityManager, configuration.getSecurityInvalidationInterval(), 
configuration.isSecurityEnabled(), configuration.getClusterUser(), 
configuration.getClusterPassword(), managementService, 
configuration.getAuthenticationCacheSize(), 
configuration.getAuthorizationCacheSize());
 
       queueFactory = new QueueFactoryImpl(executorFactory, scheduledPool, 
addressSettingsRepository, storageManager, this);
 
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
index 1c22412..f90451d 100644
--- 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQJAASSecurityManager.java
@@ -34,7 +34,6 @@ import org.apache.activemq.artemis.logs.AuditLogger;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
 import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal;
-import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal;
 import org.jboss.logging.Logger;
 
 import static 
org.apache.activemq.artemis.core.remoting.CertificateUtil.getCertsFromConnection;
@@ -45,7 +44,7 @@ import static 
org.apache.activemq.artemis.core.remoting.CertificateUtil.getCerts
  * The {@link Subject} returned by the login context is expecting to have a 
set of {@link RolePrincipal} for each
  * role of the user.
  */
-public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager4 {
+public class ActiveMQJAASSecurityManager implements ActiveMQSecurityManager5 {
 
    private static final Logger logger = 
Logger.getLogger(ActiveMQJAASSecurityManager.class);
 
@@ -95,9 +94,9 @@ public class ActiveMQJAASSecurityManager implements 
ActiveMQSecurityManager4 {
    }
 
    @Override
-   public String validateUser(final String user, final String password, 
RemotingConnection remotingConnection, final String securityDomain) {
+   public Subject authenticate(final String user, final String password, 
RemotingConnection remotingConnection, final String securityDomain) {
       try {
-         return getUserFromSubject(getAuthenticatedSubject(user, password, 
remotingConnection, securityDomain));
+         return getAuthenticatedSubject(user, password, remotingConnection, 
securityDomain);
       } catch (LoginException e) {
          if (logger.isDebugEnabled()) {
             logger.debug("Couldn't validate user", e);
@@ -106,49 +105,24 @@ public class ActiveMQJAASSecurityManager implements 
ActiveMQSecurityManager4 {
       }
    }
 
-   public String getUserFromSubject(Subject subject) {
-      String validatedUser = "";
-      Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
-
-      // should only ever be 1 UserPrincipal
-      for (UserPrincipal userPrincipal : users) {
-         validatedUser = userPrincipal.getName();
-      }
-      return validatedUser;
-   }
-
    @Override
    public boolean validateUserAndRole(String user, String password, Set<Role> 
roles, CheckType checkType) {
       throw new UnsupportedOperationException("Invoke 
validateUserAndRole(String, String, Set<Role>, CheckType, String, 
RemotingConnection, String) instead");
    }
 
    @Override
-   public String validateUserAndRole(final String user,
-                                     final String password,
-                                     final Set<Role> roles,
-                                     final CheckType checkType,
-                                     final String address,
-                                     final RemotingConnection 
remotingConnection,
-                                     final String securityDomain) {
-      Subject localSubject;
-      try {
-         localSubject = getAuthenticatedSubject(user, password, 
remotingConnection, securityDomain);
-      } catch (LoginException e) {
-         if (logger.isDebugEnabled()) {
-            logger.debug("Couldn't validate user", e);
-         }
-         return null;
-      }
-
+   public boolean authorize(final Subject subject,
+                            final Set<Role> roles,
+                            final CheckType checkType) {
       boolean authorized = false;
 
-      if (localSubject != null) {
+      if (subject != null) {
          Set<RolePrincipal> rolesWithPermission = 
getPrincipalsInRole(checkType, roles);
 
          // Check the caller's roles
          Set<Principal> rolesForSubject = new HashSet<>();
          try {
-            
rolesForSubject.addAll(localSubject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
+            
rolesForSubject.addAll(subject.getPrincipals(Class.forName(rolePrincipalClass).asSubclass(Principal.class)));
          } catch (Exception e) {
             ActiveMQServerLogger.LOGGER.failedToFindRolesForTheSubject(e);
          }
@@ -169,11 +143,7 @@ public class ActiveMQJAASSecurityManager implements 
ActiveMQSecurityManager4 {
          }
       }
 
-      if (authorized) {
-         return getUserFromSubject(localSubject);
-      } else {
-         return null;
-      }
+      return authorized;
    }
 
    private Subject getAuthenticatedSubject(final String user,
diff --git 
a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager5.java
 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager5.java
new file mode 100644
index 0000000..e043866
--- /dev/null
+++ 
b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/ActiveMQSecurityManager5.java
@@ -0,0 +1,61 @@
+/*
+ * 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.artemis.spi.core.security;
+
+import javax.security.auth.Subject;
+import java.util.Set;
+
+import org.apache.activemq.artemis.core.security.CheckType;
+import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
+
+/**
+ * Used to validate whether a user is authorized to connect to the
+ * server and perform certain functions on certain addresses
+ *
+ * This is an evolution of {@link ActiveMQSecurityManager4}
+ * that integrates with the new Subject caching functionality.
+ */
+public interface ActiveMQSecurityManager5 extends ActiveMQSecurityManager {
+
+   /**
+    * is this a valid user.
+    *
+    * This method is called instead of
+    * {@link ActiveMQSecurityManager#validateUser(String, String)}.
+    *
+    * @param user     the user
+    * @param password the user's password
+    * @param remotingConnection the user's connection which contains any 
corresponding SSL certs
+    * @param securityDomain the name of the JAAS security domain to use (can 
be null)
+    * @return the Subject of the authenticated user or null if the user isn't 
authenticated
+    */
+   Subject authenticate(String user, String password, RemotingConnection 
remotingConnection, String securityDomain);
+
+   /**
+    * Determine whether the given user has the correct role for the given 
check type.
+    *
+    * This method is called instead of
+    * {@link ActiveMQSecurityManager#validateUserAndRole(String, String, Set, 
CheckType)}.
+    *
+    * @param subject    the Subject to authorize
+    * @param roles      the roles configured in the security-settings
+    * @param checkType  which permission to validate
+    * @return true if the user is authorized, else false
+    */
+   boolean authorize(Subject subject, Set<Role> roles, CheckType checkType);
+}
diff --git a/artemis-server/src/main/resources/schema/artemis-configuration.xsd 
b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
index 6387a69..492b47c 100644
--- a/artemis-server/src/main/resources/schema/artemis-configuration.xsd
+++ b/artemis-server/src/main/resources/schema/artemis-configuration.xsd
@@ -134,7 +134,23 @@
          <xsd:element name="security-invalidation-interval" type="xsd:long" 
default="10000" maxOccurs="1" minOccurs="0">
             <xsd:annotation>
                <xsd:documentation>
-                  how long (in ms) to wait before invalidating the security 
cache
+                  how long (in ms) to wait before invalidating an entry in the 
authentication or authorization cache
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+
+         <xsd:element name="authentication-cache-size" type="xsd:long" 
default="1000" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  how large to make the authentication cache
+               </xsd:documentation>
+            </xsd:annotation>
+         </xsd:element>
+
+         <xsd:element name="authorization-cache-size" type="xsd:long" 
default="1000" maxOccurs="1" minOccurs="0">
+            <xsd:annotation>
+               <xsd:documentation>
+                  how large to make the authorization cache
                </xsd:documentation>
             </xsd:annotation>
          </xsd:element>
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
index c43618c..d34d40d 100644
--- 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/FileConfigurationTest.java
@@ -102,6 +102,8 @@ public class FileConfigurationTest extends 
ConfigurationImplTest {
       Assert.assertEquals(54321, conf.getThreadPoolMaxSize());
       Assert.assertEquals(false, conf.isSecurityEnabled());
       Assert.assertEquals(5423, conf.getSecurityInvalidationInterval());
+      Assert.assertEquals(333, conf.getAuthenticationCacheSize());
+      Assert.assertEquals(444, conf.getAuthorizationCacheSize());
       Assert.assertEquals(true, conf.isWildcardRoutingEnabled());
       Assert.assertEquals(new SimpleString("Giraffe"), 
conf.getManagementAddress());
       Assert.assertEquals(new SimpleString("Whatever"), 
conf.getManagementNotificationAddress());
diff --git 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java
 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java
index d9e535b..b2f73b1 100644
--- 
a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java
+++ 
b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/JAASSecurityManagerTest.java
@@ -16,8 +16,11 @@
  */
 package org.apache.activemq.artemis.core.security.jaas;
 
+import javax.security.auth.Subject;
+
 import org.apache.activemq.artemis.core.security.CheckType;
 import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
 import 
org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
 import org.jboss.logging.Logger;
 import org.junit.Rule;
@@ -38,6 +41,7 @@ import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 @RunWith(Parameterized.class)
 public class JAASSecurityManagerTest {
@@ -80,18 +84,17 @@ public class JAASSecurityManagerTest {
          }
          ActiveMQJAASSecurityManager securityManager = new 
ActiveMQJAASSecurityManager("PropertiesLogin");
 
-         String result = securityManager.validateUser("first", "secret", null, 
null);
+         Subject result = securityManager.authenticate("first", "secret", 
null, null);
 
          assertNotNull(result);
-         assertEquals("first", result);
+         assertEquals("first", SecurityStoreImpl.getUserFromSubject(result));
 
          Role role = new Role("programmers", true, true, true, true, true, 
true, true, true, true, true);
          Set<Role> roles = new HashSet<>();
          roles.add(role);
-         result = securityManager.validateUserAndRole("first", "secret", 
roles, CheckType.SEND, "someaddress", null, null);
+         boolean authorizationResult = securityManager.authorize(result, 
roles, CheckType.SEND);
 
-         assertNotNull(result);
-         assertEquals("first", result);
+         assertTrue(authorizationResult);
 
       } finally {
          Thread.currentThread().setContextClassLoader(existingLoader);
diff --git 
a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml 
b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
index 820d39d..2d1e190 100644
--- a/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
+++ b/artemis-server/src/test/resources/ConfigurationTest-full-config.xml
@@ -28,6 +28,8 @@
       <graceful-shutdown-enabled>true</graceful-shutdown-enabled>
       <graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
       <security-invalidation-interval>5423</security-invalidation-interval>
+      <authentication-cache-size>333</authentication-cache-size>
+      <authorization-cache-size>444</authorization-cache-size>
       <journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
       <wild-card-routing-enabled>true</wild-card-routing-enabled>
       <management-address>Giraffe</management-address>
diff --git 
a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml 
b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
index d4bdc8b..46408cf 100644
--- a/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
+++ b/artemis-server/src/test/resources/ConfigurationTest-xinclude-config.xml
@@ -29,6 +29,8 @@
       <graceful-shutdown-enabled>true</graceful-shutdown-enabled>
       <graceful-shutdown-timeout>12345</graceful-shutdown-timeout>
       <security-invalidation-interval>5423</security-invalidation-interval>
+      <authentication-cache-size>333</authentication-cache-size>
+      <authorization-cache-size>444</authorization-cache-size>
       <journal-lock-acquisition-timeout>123</journal-lock-acquisition-timeout>
       <wild-card-routing-enabled>true</wild-card-routing-enabled>
       <management-address>Giraffe</management-address>
diff --git a/docs/user-manual/en/security.md b/docs/user-manual/en/security.md
index 5bf08bf..7ba7da8 100644
--- a/docs/user-manual/en/security.md
+++ b/docs/user-manual/en/security.md
@@ -6,9 +6,17 @@ you can configure it.
 To disable security completely simply set the `security-enabled` property to
 `false` in the `broker.xml` file.
 
-For performance reasons security is cached and invalidated every so long. To
-change this period set the property `security-invalidation-interval`, which is
-in milliseconds. The default is `10000` ms.
+For performance reasons both **authentication and authorization is cached**
+independently. Entries are removed from the caches (i.e. invalidated) either
+when the cache reaches its maximum size in which case the least-recently used
+entry is removed or when an entry has been in the cache "too long".
+
+The size of the caches are controlled by the `authentication-cache-size` and
+`authorization-cache-size` configuration parameters. Both deafult to `1000`.
+
+How long cache entries are valid is controlled by
+`security-invalidation-interval`, which is in milliseconds. Using `0` will
+disable caching. The default is `10000` ms.
 
 ## Tracking the Validated User
 
diff --git 
a/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java
 
b/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java
index 053fc02..251e467 100644
--- 
a/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java
+++ 
b/examples/features/standard/security-manager/src/main/java/org/apache/activemq/artemis/jms/example/JAASSecurityManagerWrapper.java
@@ -17,6 +17,7 @@
 
 package org.apache.activemq.artemis.jms.example;
 
+import javax.security.auth.Subject;
 import java.util.Map;
 import java.util.Set;
 
@@ -25,28 +26,23 @@ import org.apache.activemq.artemis.core.security.Role;
 import org.apache.activemq.artemis.spi.core.protocol.RemotingConnection;
 import 
org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
 import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager;
-import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
+import org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager5;
 
-public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager4 {
+public class JAASSecurityManagerWrapper implements ActiveMQSecurityManager5 {
    ActiveMQJAASSecurityManager activeMQJAASSecurityManager;
 
    @Override
-   public String validateUser(String user, String password, RemotingConnection 
remotingConnection, String securityDomain) {
-      System.out.println("validateUser(" + user + ", " + password + ", " + 
remotingConnection.getRemoteAddress() + ")");
-      return activeMQJAASSecurityManager.validateUser(user, password, 
remotingConnection, securityDomain);
+   public Subject authenticate(String user, String password, 
RemotingConnection remotingConnection, String securityDomain) {
+      System.out.println("authenticate(" + user + ", " + password + ", " + 
remotingConnection.getRemoteAddress() + ")");
+      return activeMQJAASSecurityManager.authenticate(user, password, 
remotingConnection, securityDomain);
    }
 
-
    @Override
-   public String validateUserAndRole(String user,
-                              String password,
-                              Set<Role> roles,
-                              CheckType checkType,
-                              String address,
-                              RemotingConnection remotingConnection,
-                              String securityDomain) {
-      System.out.println("validateUserAndRole(" + user + ", " + password + ", 
" + roles + ", " + checkType + ", " + address + ", " + 
remotingConnection.getRemoteAddress() + ")");
-      return activeMQJAASSecurityManager.validateUserAndRole(user, password, 
roles, checkType, address, remotingConnection, securityDomain);
+   public boolean authorize(Subject subject,
+                            Set<Role> roles,
+                            CheckType checkType) {
+      System.out.println("authorize(" + subject + ", " + roles + ", " + 
checkType + ")");
+      return activeMQJAASSecurityManager.authorize(subject, roles, checkType);
    }
 
    @Override
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
index 9347cc2..05faa6a 100644
--- 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlTest.java
@@ -198,6 +198,32 @@ public class ActiveMQServerControlTest extends 
ManagementTestBase {
    }
 
    @Test
+   public void testSecurityCacheSizes() throws Exception {
+      ActiveMQServerControl serverControl = createManagementControl();
+
+      Assert.assertEquals(usingCore() ? 1 : 0, 
serverControl.getAuthenticationCacheSize());
+      Assert.assertEquals(usingCore() ? 7 : 0, 
serverControl.getAuthorizationCacheSize());
+
+      ServerLocator loc = createInVMNonHALocator();
+      ClientSessionFactory csf = createSessionFactory(loc);
+      ClientSession session = csf.createSession("myUser", "myPass", false, 
true, false, false, 0);
+      session.start();
+
+      final String address = "ADDRESS";
+
+      serverControl.createAddress(address, "MULTICAST");
+
+      ClientProducer producer = session.createProducer(address);
+
+      ClientMessage m = session.createMessage(true);
+      m.putStringProperty("hello", "world");
+      producer.send(m);
+
+      Assert.assertEquals(usingCore() ? 2 : 1, 
serverControl.getAuthenticationCacheSize());
+      Assert.assertEquals(usingCore() ? 8 : 1, 
serverControl.getAuthorizationCacheSize());
+   }
+
+   @Test
    public void testGetConnectors() throws Exception {
       ActiveMQServerControl serverControl = createManagementControl();
 
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
index 95f0513..313084c 100644
--- 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/management/ActiveMQServerControlUsingCoreTest.java
@@ -769,6 +769,16 @@ public class ActiveMQServerControlUsingCoreTest extends 
ActiveMQServerControlTes
          }
 
          @Override
+         public long getAuthenticationCacheSize() {
+            return (Long) 
proxy.retrieveAttributeValue("AuthenticationCacheSize", Long.class);
+         }
+
+         @Override
+         public long getAuthorizationCacheSize() {
+            return (Long) 
proxy.retrieveAttributeValue("AuthorizationCacheSize", Long.class);
+         }
+
+         @Override
          public double getDiskStoreUsage() {
             try {
                return (Double) proxy.invokeOperation("getDiskStoreUsage");
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java
index 87cf781..8535cb0 100644
--- 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/security/SecurityTest.java
@@ -51,6 +51,7 @@ import 
org.apache.activemq.artemis.core.remoting.impl.invm.InVMConnection;
 import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
 import org.apache.activemq.artemis.core.security.CheckType;
 import org.apache.activemq.artemis.core.security.Role;
+import org.apache.activemq.artemis.core.security.impl.SecurityStoreImpl;
 import org.apache.activemq.artemis.core.server.ActiveMQServer;
 import org.apache.activemq.artemis.core.server.ActiveMQServers;
 import org.apache.activemq.artemis.core.server.Queue;
@@ -66,6 +67,7 @@ import 
org.apache.activemq.artemis.spi.core.security.ActiveMQSecurityManager4;
 import org.apache.activemq.artemis.tests.util.ActiveMQTestBase;
 import org.apache.activemq.artemis.tests.util.CreateMessage;
 import org.apache.activemq.artemis.utils.CompositeAddress;
+import org.apache.activemq.artemis.utils.Wait;
 import org.apache.activemq.command.ActiveMQQueue;
 import org.junit.Assert;
 import org.junit.Before;
@@ -1412,6 +1414,9 @@ public class SecurityTest extends ActiveMQTestBase {
 
       securityManager.getConfiguration().addRole("auser", "receiver");
 
+      // invalidate the authentication cache so the new role will be picked up
+      
((SecurityStoreImpl)server.getSecurityStore()).invalidateAuthenticationCache();
+
       session.createConsumer(SecurityTest.queueA);
 
       // Removing the Role... the check should be cached, so the next 
createConsumer shouldn't fail
@@ -1483,7 +1488,7 @@ public class SecurityTest extends ActiveMQTestBase {
 
    @Test
    public void testSendMessageUpdateSender() throws Exception {
-      Configuration configuration = 
createDefaultInVMConfig().setSecurityEnabled(true).setSecurityInvalidationInterval(-1);
+      Configuration configuration = 
createDefaultInVMConfig().setSecurityEnabled(true).setSecurityInvalidationInterval(1000);
       ActiveMQServer server = createServer(false, configuration);
       server.start();
       HierarchicalRepository<Set<Role>> securityRepository = 
server.getSecurityRepository();
@@ -1518,7 +1523,14 @@ public class SecurityTest extends ActiveMQTestBase {
 
       securityManager.getConfiguration().addRole("auser", "receiver");
 
-      session.createConsumer(SecurityTest.queueA);
+      Wait.assertTrue(() -> {
+         try {
+            session.createConsumer(SecurityTest.queueA);
+            return true;
+         } catch (Exception e) {
+            return false;
+         }
+      }, 2000, 100);
 
       // Removing the Role... the check should be cached... but we used
       // setSecurityInvalidationInterval(0), so the
diff --git 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java
 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java
index 22f123a..070d87c 100644
--- 
a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java
+++ 
b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/stomp/StompWithClientIdValidationTest.java
@@ -17,6 +17,7 @@
 
 package org.apache.activemq.artemis.tests.integration.stomp;
 
+import javax.security.auth.Subject;
 import java.lang.management.ManagementFactory;
 
 import org.apache.activemq.artemis.api.core.TransportConfiguration;
@@ -46,13 +47,14 @@ public class StompWithClientIdValidationTest extends 
StompTestBase {
          .setSecurityEnabled(isSecurityEnabled())
          .setPersistenceEnabled(isPersistenceEnabled())
          .addAcceptorConfiguration("stomp", 
"tcp://localhost:61613?enabledProtocols=STOMP")
-         .addAcceptorConfiguration(new 
TransportConfiguration(InVMAcceptorFactory.class.getName()));
+         .addAcceptorConfiguration(new 
TransportConfiguration(InVMAcceptorFactory.class.getName()))
+         .setSecurityInvalidationInterval(0); // disable caching
 
       ActiveMQJAASSecurityManager securityManager = new 
ActiveMQJAASSecurityManager(InVMLoginModule.class.getName(), new 
SecurityConfiguration()) {
          @Override
-         public String validateUser(String user, String password, 
RemotingConnection remotingConnection, String securityDomain) {
+         public Subject authenticate(String user, String password, 
RemotingConnection remotingConnection, String securityDomain) {
 
-            String validatedUser = super.validateUser(user, password, 
remotingConnection, securityDomain);
+            Subject validatedUser = super.authenticate(user, password, 
remotingConnection, securityDomain);
             if (validatedUser == null) {
                return null;
             }

Reply via email to