You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@activemq.apache.org by cl...@apache.org on 2015/11/09 21:41:45 UTC

[3/4] activemq-artemis git commit: ARTEMIS-299 file-based login module performance

ARTEMIS-299 file-based login module performance

This is based on improvements made in ActiveMQ 5.x. See more at
https://issues.apache.org/jira/browse/AMQ-5876.


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

Branch: refs/heads/master
Commit: ae70d7178e201dff5b13ac9c6cb63146d29fc4b9
Parents: 08e5245
Author: jbertram <jb...@apache.org>
Authored: Wed Nov 4 16:38:40 2015 -0600
Committer: jbertram <jb...@apache.org>
Committed: Thu Nov 5 10:10:49 2015 -0600

----------------------------------------------------------------------
 .../security/jaas/CertificateLoginModule.java   |      9 +-
 .../core/security/jaas/PrincipalProperties.java |     75 -
 .../core/security/jaas/PropertiesLoader.java    |    137 +
 .../security/jaas/PropertiesLoginModule.java    |     86 +-
 .../security/jaas/ReloadableProperties.java     |     99 +
 .../jaas/TextFileCertificateLoginModule.java    |     62 +-
 .../PropertiesLoginModuleRaceConditionTest.java |      3 +-
 .../core/security/jaas/StubX509Certificate.java |    137 +
 .../TextFileCertificateLoginModuleTest.java     |    123 +
 .../src/test/resources/cert-roles.properties    |     16 +
 .../test/resources/cert-users-LARGE.properties  | 200026 ++++++++++++++++
 .../test/resources/cert-users-SMALL.properties  |    116 +
 12 files changed, 200682 insertions(+), 207 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
index fbacb31..20d107e 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/CertificateLoginModule.java
@@ -37,7 +37,7 @@ import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
  * Allows for subclasses to define methods used to verify user certificates and
  * find user roles. Uses CertificateCallbacks to retrieve certificates.
  */
-public abstract class CertificateLoginModule implements LoginModule {
+public abstract class CertificateLoginModule extends PropertiesLoader implements LoginModule {
 
    private CallbackHandler callbackHandler;
    private Subject subject;
@@ -46,7 +46,6 @@ public abstract class CertificateLoginModule implements LoginModule {
    private String username;
    private Set<String> roles;
    private Set<Principal> principals = new HashSet<Principal>();
-   private boolean debug;
 
    /**
     * Overriding to allow for proper initialization. Standard JAAS.
@@ -56,11 +55,7 @@ public abstract class CertificateLoginModule implements LoginModule {
       this.subject = subject;
       this.callbackHandler = callbackHandler;
 
-      debug = "true".equalsIgnoreCase((String) options.get("debug"));
-
-      if (debug) {
-         ActiveMQServerLogger.LOGGER.debug("Initialized debug");
-      }
+      init(options);
    }
 
    /**

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java
deleted file mode 100644
index 97e2355..0000000
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PrincipalProperties.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.jaas;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-
-import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
-
-class PrincipalProperties {
-
-   private final Properties principals;
-   private final long reloadTime;
-
-   PrincipalProperties(final String type, final File source, final ActiveMQServerLogger log) {
-      Properties props = new Properties();
-      long reloadTime = 0;
-      try {
-         load(source, props);
-         reloadTime = System.currentTimeMillis();
-      }
-      catch (IOException ioe) {
-         ioe.printStackTrace();
-         log.warn("Unable to load " + type + " properties file " + source);
-      }
-      this.reloadTime = reloadTime;
-      this.principals = props;
-   }
-
-   @SuppressWarnings({"unchecked", "rawtypes"})
-   Set<Map.Entry<String, String>> entries() {
-      return (Set) principals.entrySet();
-   }
-
-   String getProperty(String name) {
-      return principals.getProperty(name);
-   }
-
-   long getReloadTime() {
-      return reloadTime;
-   }
-
-   private void load(final File source, Properties props) throws FileNotFoundException, IOException {
-      FileInputStream in = new FileInputStream(source);
-      try {
-         props.load(in);
-      }
-      finally {
-         in.close();
-      }
-   }
-
-   Properties getPrincipals() {
-      return principals;
-   }
-}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.java
new file mode 100644
index 0000000..89b80db
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoader.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.artemis.spi.core.security.jaas;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+public class PropertiesLoader {
+
+   static Map<FileNameKey, ReloadableProperties> staticCache = new HashMap<FileNameKey, ReloadableProperties>();
+   protected boolean debug;
+
+   public void init(Map options) {
+      debug = booleanOption("debug", options);
+      if (debug) {
+         ActiveMQServerLogger.LOGGER.debug("Initialized debug");
+      }
+   }
+
+   public ReloadableProperties load(String nameProperty, String fallbackName, Map options) {
+      ReloadableProperties result;
+      FileNameKey key = new FileNameKey(nameProperty, fallbackName, options);
+      key.setDebug(debug);
+
+      synchronized (staticCache) {
+         result = staticCache.get(key);
+         if (result == null) {
+            result = new ReloadableProperties(key);
+            staticCache.put(key, result);
+         }
+      }
+
+      return result.obtained();
+   }
+
+   private static boolean booleanOption(String name, Map options) {
+      return Boolean.parseBoolean((String) options.get(name));
+   }
+
+   public class FileNameKey {
+
+      final File file;
+      final String absPath;
+      final boolean reload;
+      private boolean decrypt;
+      private boolean debug;
+
+      public FileNameKey(String nameProperty, String fallbackName, Map options) {
+         this.file = new File(baseDir(options), stringOption(nameProperty, fallbackName, options));
+         absPath = file.getAbsolutePath();
+         reload = booleanOption("reload", options);
+         decrypt = booleanOption("decrypt", options);
+      }
+
+      @Override
+      public boolean equals(Object other) {
+         return other instanceof FileNameKey && this.absPath.equals(((FileNameKey) other).absPath);
+      }
+
+      public int hashCode() {
+         return this.absPath.hashCode();
+      }
+
+      public boolean isReload() {
+         return reload;
+      }
+
+      public File file() {
+         return file;
+      }
+
+      public boolean isDecrypt() {
+         return decrypt;
+      }
+
+      public void setDecrypt(boolean decrypt) {
+         this.decrypt = decrypt;
+      }
+
+      private String stringOption(String key, String nameDefault, Map options) {
+         Object result = options.get(key);
+         return result != null ? result.toString() : nameDefault;
+      }
+
+      private File baseDir(Map options) {
+         File baseDir = null;
+         if (options.get("baseDir") != null) {
+            baseDir = new File((String) options.get("baseDir"));
+         }
+         else {
+            if (System.getProperty("java.security.auth.login.config") != null) {
+               baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
+            }
+         }
+         if (debug) {
+            ActiveMQServerLogger.LOGGER.debug("Using basedir=" + baseDir.getAbsolutePath());
+         }
+         return baseDir;
+      }
+
+      public String toString() {
+         return "PropsFile=" + absPath;
+      }
+
+      public void setDebug(boolean debug) {
+         this.debug = debug;
+      }
+
+      public boolean isDebug() {
+         return debug;
+      }
+   }
+
+   /**
+    * For test-usage only.
+    */
+   public static void resetUsersAndGroupsCache() {
+      staticCache.clear();
+   }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
index 6b96ed0..0a75a24 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/PropertiesLoginModule.java
@@ -25,87 +25,41 @@ import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.FailedLoginException;
 import javax.security.auth.login.LoginException;
 import javax.security.auth.spi.LoginModule;
-import java.io.File;
 import java.io.IOException;
 import java.security.Principal;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 
 import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
 
-public class PropertiesLoginModule implements LoginModule {
+public class PropertiesLoginModule extends PropertiesLoader implements LoginModule {
 
-   private static final String USER_FILE = "org.apache.activemq.jaas.properties.user";
-   private static final String GROUP_FILE = "org.apache.activemq.jaas.properties.role";
+   private static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.user";
+   private static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.properties.role";
 
    private Subject subject;
    private CallbackHandler callbackHandler;
 
-   private boolean debug;
-   private boolean reload = false;
-   private static volatile PrincipalProperties users;
-   private static volatile PrincipalProperties roles;
+   private Properties users;
+   private Properties roles;
    private String user;
    private final Set<Principal> principals = new HashSet<Principal>();
-   private File baseDir;
    private boolean loginSucceeded;
-   //    private boolean decrypt = true;
 
    @Override
-   public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
+   public void initialize(Subject subject,
+                          CallbackHandler callbackHandler,
+                          Map sharedState,
+                          Map options) {
       this.subject = subject;
       this.callbackHandler = callbackHandler;
       loginSucceeded = false;
 
-      debug = "true".equalsIgnoreCase((String) options.get("debug"));
-      if (options.get("reload") != null) {
-         reload = "true".equalsIgnoreCase((String) options.get("reload"));
-      }
-
-      if (options.get("baseDir") != null) {
-         baseDir = new File((String) options.get("baseDir"));
-      }
-
-      setBaseDir();
-      String usersFile = options.get(USER_FILE) + "";
-      File uf = baseDir != null ? new File(baseDir, usersFile) : new File(usersFile);
-
-      if (reload || users == null || uf.lastModified() > users.getReloadTime()) {
-         if (debug) {
-            ActiveMQServerLogger.LOGGER.debug("Reloading users from " + uf.getAbsolutePath());
-         }
-         users = new PrincipalProperties("user", uf, ActiveMQServerLogger.LOGGER);
-         //            if( decrypt ) {
-         //                try {
-         //                    EncryptionSupport.decrypt(users.getPrincipals());
-         //                } catch(NoClassDefFoundError e) {
-         //                    // this Happens whe jasypt is not on the classpath..
-         //                    decrypt = false;
-         //                    ActiveMQServerLogger.LOGGER.info("jasypt is not on the classpath: password decryption disabled.");
-         //                }
-         //            }
-      }
-
-      String groupsFile = options.get(GROUP_FILE) + "";
-      File gf = baseDir != null ? new File(baseDir, groupsFile) : new File(groupsFile);
-      if (reload || roles == null || gf.lastModified() > roles.getReloadTime()) {
-         if (debug) {
-            ActiveMQServerLogger.LOGGER.debug("Reloading roles from " + gf.getAbsolutePath());
-         }
-         roles = new PrincipalProperties("role", gf, ActiveMQServerLogger.LOGGER);
-      }
-   }
-
-   private void setBaseDir() {
-      if (baseDir == null) {
-         if (System.getProperty("java.security.auth.login.config") != null) {
-            baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
-            if (debug) {
-               ActiveMQServerLogger.LOGGER.debug("Using basedir=" + baseDir.getAbsolutePath());
-            }
-         }
-      }
+      init(options);
+      users = load(USER_FILE_PROP_NAME, "user", options).getProps();
+      roles = load(ROLE_FILE_PROP_NAME, "role", options).getProps();
    }
 
    @Override
@@ -153,12 +107,12 @@ public class PropertiesLoginModule implements LoginModule {
       if (result) {
          principals.add(new UserPrincipal(user));
 
-         for (Map.Entry<String, String> entry : roles.entries()) {
-            String name = entry.getKey();
+         for (Map.Entry<Object, Object> entry : roles.entrySet()) {
+            String name = (String) entry.getKey();
+            String[] userList = ((String) entry.getValue()).split(",");
             if (debug) {
                ActiveMQServerLogger.LOGGER.debug("Inspecting role '" + name + "' with user(s): " + entry.getValue());
             }
-            String[] userList = entry.getValue().split(",");
             for (int i = 0; i < userList.length; i++) {
                if (user.equals(userList[i])) {
                   principals.add(new RolePrincipal(name));
@@ -204,12 +158,4 @@ public class PropertiesLoginModule implements LoginModule {
       user = null;
       loginSucceeded = false;
    }
-
-   /**
-    * For test-usage only.
-    */
-   public static void resetUsersAndGroupsCache() {
-      users = null;
-      roles = null;
-   }
 }

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
new file mode 100644
index 0000000..46d10a5
--- /dev/null
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java
@@ -0,0 +1,99 @@
+/*
+ * 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.jaas;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+
+public class ReloadableProperties {
+
+   private Properties props = new Properties();
+   private Map<String, String> invertedProps;
+   private long reloadTime = -1;
+   private final PropertiesLoader.FileNameKey key;
+
+   public ReloadableProperties(PropertiesLoader.FileNameKey key) {
+      this.key = key;
+   }
+
+   public synchronized Properties getProps() {
+      return props;
+   }
+
+   public synchronized ReloadableProperties obtained() {
+      if (reloadTime < 0 || (key.isReload() && hasModificationAfter(reloadTime))) {
+         props = new Properties();
+         try {
+            load(key.file(), props);
+            invertedProps = null;
+            if (key.isDebug()) {
+               ActiveMQServerLogger.LOGGER.debug("Load of: " + key);
+            }
+         }
+         catch (IOException e) {
+            ActiveMQServerLogger.LOGGER.error("Failed to load: " + key + ", reason:" + e.getLocalizedMessage());
+            if (key.isDebug()) {
+               ActiveMQServerLogger.LOGGER.debug("Load of: " + key + ", failure exception" + e);
+            }
+         }
+         reloadTime = System.currentTimeMillis();
+      }
+      return this;
+   }
+
+   public synchronized Map<String, String> invertedPropertiesMap() {
+      if (invertedProps == null) {
+         invertedProps = new HashMap<>(props.size());
+         for (Map.Entry<Object, Object> val : props.entrySet()) {
+            invertedProps.put((String) val.getValue(), (String) val.getKey());
+         }
+      }
+      return invertedProps;
+   }
+
+   private void load(final File source,
+                     Properties props) throws IOException {
+      FileInputStream in = new FileInputStream(source);
+      try {
+         props.load(in);
+         //            if (key.isDecrypt()) {
+         //                try {
+         //                    EncryptionSupport.decrypt(this.props);
+         //                } catch (NoClassDefFoundError e) {
+         //                    // this Happens whe jasypt is not on the classpath..
+         //                    key.setDecrypt(false);
+         //                    ActiveMQServerLogger.LOGGER.info("jasypt is not on the classpath: password decryption disabled.");
+         //                }
+         //            }
+
+      }
+      finally {
+         in.close();
+      }
+   }
+
+   private boolean hasModificationAfter(long reloadTime) {
+      return key.file.lastModified() > reloadTime;
+   }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
index 68639db..b1e4ba7 100644
--- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
+++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/TextFileCertificateLoginModule.java
@@ -20,8 +20,6 @@ import javax.security.auth.Subject;
 import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.login.LoginException;
 import javax.security.cert.X509Certificate;
-import java.io.File;
-import java.io.IOException;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Map;
@@ -37,16 +35,15 @@ import java.util.Set;
  * org.apache.activemq.jaas.textfiledn.user and
  * org.apache.activemq.jaas.textfiledn.role properties respectively. NOTE: This
  * class will re-read user and group files for every authentication (i.e it does
- * live updates of allowed groups and users).
+ * live updates of allowed roles and users).
  */
 public class TextFileCertificateLoginModule extends CertificateLoginModule {
 
-   private static final String USER_FILE = "org.apache.activemq.jaas.textfiledn.user";
-   private static final String ROLE_FILE = "org.apache.activemq.jaas.textfiledn.role";
+   private static final String USER_FILE_PROP_NAME = "org.apache.activemq.jaas.textfiledn.user";
+   private static final String ROLE_FILE_PROP_NAME = "org.apache.activemq.jaas.textfiledn.role";
 
-   private File baseDir;
-   private String usersFilePathname;
-   private String rolesFilePathname;
+   private Properties roles;
+   private Map<String, String> usersByDn;
 
    /**
     * Performs initialization of file paths. A standard JAAS override.
@@ -54,15 +51,8 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) {
       super.initialize(subject, callbackHandler, sharedState, options);
-      if (System.getProperty("java.security.auth.login.config") != null) {
-         baseDir = new File(System.getProperty("java.security.auth.login.config")).getParentFile();
-      }
-      else {
-         baseDir = new File(".");
-      }
-
-      usersFilePathname = options.get(USER_FILE) + "";
-      rolesFilePathname = options.get(ROLE_FILE) + "";
+      usersByDn = load(USER_FILE_PROP_NAME, "", options).invertedPropertiesMap();
+      roles = load(ROLE_FILE_PROP_NAME, "", options).getProps();
    }
 
    /**
@@ -81,32 +71,7 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
          throw new LoginException("Client certificates not found. Cannot authenticate.");
       }
 
-      File usersFile = new File(baseDir, usersFilePathname);
-
-      Properties users = new Properties();
-
-      try {
-         java.io.FileInputStream in = new java.io.FileInputStream(usersFile);
-         users.load(in);
-         in.close();
-      }
-      catch (IOException ioe) {
-         throw new LoginException("Unable to load user properties file " + usersFile);
-      }
-
-      String dn = getDistinguishedName(certs);
-
-      Enumeration<Object> keys = users.keys();
-      for (Enumeration<Object> vals = users.elements(); vals.hasMoreElements(); ) {
-         if (vals.nextElement().equals(dn)) {
-            return (String) keys.nextElement();
-         }
-         else {
-            keys.nextElement();
-         }
-      }
-
-      return null;
+      return usersByDn.get(getDistinguishedName(certs));
    }
 
    /**
@@ -119,17 +84,6 @@ public class TextFileCertificateLoginModule extends CertificateLoginModule {
     */
    @Override
    protected Set<String> getUserRoles(String username) throws LoginException {
-      File rolesFile = new File(baseDir, rolesFilePathname);
-
-      Properties roles = new Properties();
-      try {
-         java.io.FileInputStream in = new java.io.FileInputStream(rolesFile);
-         roles.load(in);
-         in.close();
-      }
-      catch (IOException ioe) {
-         throw new LoginException("Unable to load role properties file " + rolesFile);
-      }
       Set<String> userRoles = new HashSet<String>();
       for (Enumeration<Object> enumeration = roles.keys(); enumeration.hasMoreElements(); ) {
          String groupName = (String) enumeration.nextElement();

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java
index eb71c4c..13907f8 100644
--- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleRaceConditionTest.java
@@ -33,6 +33,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
+import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
 import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoginModule;
 import org.junit.After;
 import org.junit.Before;
@@ -126,7 +127,7 @@ public class PropertiesLoginModuleRaceConditionTest {
    public void after() throws InterruptedException {
       pool.shutdown();
       assertTrue(pool.awaitTermination(500, TimeUnit.SECONDS));
-      PropertiesLoginModule.resetUsersAndGroupsCache();
+      PropertiesLoader.resetUsersAndGroupsCache();
    }
 
    @Test

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubX509Certificate.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubX509Certificate.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubX509Certificate.java
new file mode 100644
index 0000000..72295f1
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/StubX509Certificate.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.artemis.core.security.jaas;
+
+import javax.security.cert.X509Certificate;
+import java.math.BigInteger;
+import java.security.Principal;
+import java.security.PublicKey;
+import java.util.Date;
+import java.util.Set;
+
+public class StubX509Certificate extends X509Certificate {
+
+   private final Principal id;
+
+   public StubX509Certificate(Principal id) {
+      this.id = id;
+   }
+
+   public Principal getSubjectDN() {
+      return this.id;
+   }
+
+   // --- Stubbed Methods ---
+   public void checkValidity() {
+   }
+
+   public void checkValidity(Date arg0) {
+   }
+
+   public int getVersion() {
+      return 0;
+   }
+
+   public BigInteger getSerialNumber() {
+      return null;
+   }
+
+   public Principal getIssuerDN() {
+      return null;
+   }
+
+   public Date getNotBefore() {
+      return null;
+   }
+
+   public Date getNotAfter() {
+      return null;
+   }
+
+   public byte[] getTBSCertificate() {
+      return null;
+   }
+
+   public byte[] getSignature() {
+      return null;
+   }
+
+   public String getSigAlgName() {
+      return null;
+   }
+
+   public String getSigAlgOID() {
+      return null;
+   }
+
+   public byte[] getSigAlgParams() {
+      return null;
+   }
+
+   public boolean[] getIssuerUniqueID() {
+      return null;
+   }
+
+   public boolean[] getSubjectUniqueID() {
+      return null;
+   }
+
+   public boolean[] getKeyUsage() {
+      return null;
+   }
+
+   public int getBasicConstraints() {
+      return 0;
+   }
+
+   public byte[] getEncoded() {
+      return null;
+   }
+
+   public void verify(PublicKey arg0) {
+   }
+
+   public void verify(PublicKey arg0, String arg1) {
+   }
+
+   public String toString() {
+      return null;
+   }
+
+   public PublicKey getPublicKey() {
+      return null;
+   }
+
+   public boolean hasUnsupportedCriticalExtension() {
+      return false;
+   }
+
+   @SuppressWarnings({"unchecked", "rawtypes"})
+   public Set getCriticalExtensionOIDs() {
+      return null;
+   }
+
+   @SuppressWarnings({"unchecked", "rawtypes"})
+   public Set getNonCriticalExtensionOIDs() {
+      return null;
+   }
+
+   public byte[] getExtensionValue(String arg0) {
+      return null;
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/TextFileCertificateLoginModuleTest.java
----------------------------------------------------------------------
diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/TextFileCertificateLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/TextFileCertificateLoginModuleTest.java
new file mode 100644
index 0000000..0eb736c
--- /dev/null
+++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/TextFileCertificateLoginModuleTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.core.security.jaas;
+
+import javax.management.remote.JMXPrincipal;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+import javax.security.cert.X509Certificate;
+import java.net.URL;
+import java.util.HashMap;
+
+import org.apache.activemq.artemis.core.server.ActiveMQServerLogger;
+import org.apache.activemq.artemis.spi.core.security.jaas.CertificateLoginModule;
+import org.apache.activemq.artemis.spi.core.security.jaas.JaasCallbackHandler;
+import org.apache.activemq.artemis.spi.core.security.jaas.PropertiesLoader;
+import org.apache.activemq.artemis.spi.core.security.jaas.TextFileCertificateLoginModule;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TextFileCertificateLoginModuleTest {
+
+   private static final String CERT_USERS_FILE_SMALL = "cert-users-SMALL.properties";
+   private static final String CERT_USERS_FILE_LARGE = "cert-users-LARGE.properties";
+   private static final String CERT_GROUPS_FILE = "cert-roles.properties";
+
+   private static final int NUMBER_SUBJECTS = 10;
+
+   static {
+      String path = System.getProperty("java.security.auth.login.config");
+      if (path == null) {
+         URL resource = TextFileCertificateLoginModuleTest.class.getClassLoader().getResource("login.config");
+         if (resource != null) {
+            path = resource.getFile();
+            System.setProperty("java.security.auth.login.config", path);
+         }
+      }
+   }
+
+   private CertificateLoginModule loginModule;
+
+   @Before
+   public void setUp() throws Exception {
+      loginModule = new TextFileCertificateLoginModule();
+   }
+
+   @After
+   public void tearDown() throws Exception {
+      PropertiesLoader.resetUsersAndGroupsCache();
+   }
+
+   @Test
+   public void testLoginWithSMALLUsersFile() throws Exception {
+      loginTest(CERT_USERS_FILE_SMALL, CERT_GROUPS_FILE);
+   }
+
+   @Test
+   public void testLoginWithLARGEUsersFile() throws Exception {
+      loginTest(CERT_USERS_FILE_LARGE, CERT_GROUPS_FILE);
+   }
+
+   private void loginTest(String usersFiles, String groupsFile) throws LoginException {
+
+      HashMap options = new HashMap<String, String>();
+      options.put("org.apache.activemq.jaas.textfiledn.user", usersFiles);
+      options.put("org.apache.activemq.jaas.textfiledn.role", groupsFile);
+      options.put("reload", "true");
+
+      JaasCallbackHandler[] callbackHandlers = new JaasCallbackHandler[NUMBER_SUBJECTS];
+      Subject[] subjects = new Subject[NUMBER_SUBJECTS];
+
+      for (int i = 0; i < callbackHandlers.length; i++) {
+         callbackHandlers[i] = getJaasCertificateCallbackHandler("DN=TEST_USER_" + (i + 1));
+      }
+
+      long startTime = System.currentTimeMillis();
+
+      for (int outer = 0; outer < 500; outer++) {
+         for (int i = 0; i < NUMBER_SUBJECTS; i++) {
+            Subject subject = doAuthenticate(options, callbackHandlers[i]);
+            subjects[i] = subject;
+         }
+      }
+
+      long endTime = System.currentTimeMillis();
+      long timeTaken = endTime - startTime;
+
+      for (int i = 0; i < NUMBER_SUBJECTS; i++) {
+         ActiveMQServerLogger.LOGGER.info("subject is: " + subjects[i].getPrincipals().toString());
+      }
+
+      ActiveMQServerLogger.LOGGER.info(usersFiles + ": Time taken is " + timeTaken);
+
+   }
+
+   private JaasCallbackHandler getJaasCertificateCallbackHandler(String user) {
+      JMXPrincipal principal = new JMXPrincipal(user);
+      X509Certificate cert = new StubX509Certificate(principal);
+      return new JaasCallbackHandler(null, null, new X509Certificate[]{cert});
+   }
+
+   private Subject doAuthenticate(HashMap options, JaasCallbackHandler callbackHandler) throws LoginException {
+      Subject mySubject = new Subject();
+      loginModule.initialize(mySubject, callbackHandler, null, options);
+      loginModule.login();
+      loginModule.commit();
+      return mySubject;
+   }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/activemq-artemis/blob/ae70d717/artemis-server/src/test/resources/cert-roles.properties
----------------------------------------------------------------------
diff --git a/artemis-server/src/test/resources/cert-roles.properties b/artemis-server/src/test/resources/cert-roles.properties
new file mode 100644
index 0000000..aab571c
--- /dev/null
+++ b/artemis-server/src/test/resources/cert-roles.properties
@@ -0,0 +1,16 @@
+## ---------------------------------------------------------------------------
+## 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.
+## ---------------------------------------------------------------------------