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.
This commit is contained in:
parent
b85156cc27
commit
90853409a0
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -680,6 +686,20 @@ public final class ActiveMQDefaultConfiguration {
|
|||
return DEFAULT_SECURITY_INVALIDATION_INTERVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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-->
|
||||
|
||||
|
|
|
@ -48,6 +48,10 @@
|
|||
<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>
|
||||
<artifactId>jboss-logging</artifactId>
|
||||
|
|
|
@ -211,6 +211,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}.
|
||||
|
|
|
@ -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();
|
||||
|
@ -506,6 +510,28 @@ public class ConfigurationImpl implements Configuration, Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
@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;
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
@ -757,6 +758,22 @@ public class ActiveMQServerControlImpl extends AbstractControl implements Active
|
|||
return (int) result;
|
||||
}
|
||||
|
||||
@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()) {
|
||||
|
|
|
@ -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,8 +154,27 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
|||
|
||||
String validatedUser = null;
|
||||
boolean userIsValid = false;
|
||||
boolean check = true;
|
||||
|
||||
if (securityManager instanceof ActiveMQSecurityManager4) {
|
||||
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 (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);
|
||||
|
@ -152,14 +183,12 @@ public class SecurityStoreImpl implements SecurityStore, HierarchicalRepositoryC
|
|||
} else {
|
||||
userIsValid = securityManager.validateUser(user, password);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// authentication failed, send a notification & throw an exception
|
||||
if (!userIsValid && validatedUser == null) {
|
||||
String certSubjectDN = getCertSubjectDN(connection);
|
||||
|
||||
if (notificationService != null) {
|
||||
TypedProperties props = new TypedProperties();
|
||||
props.putSimpleStringProperty(ManagementHelper.HDR_USER, SimpleString.toSimpleString(user));
|
||||
|
@ -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 --------------------------------------------------------
|
||||
|
||||
// Protected -----------------------------------------------------
|
||||
|
||||
// Package Private -----------------------------------------------
|
||||
|
||||
// Private -------------------------------------------------------
|
||||
private void invalidateCache() {
|
||||
cache.clear();
|
||||
public static String getUserFromSubject(Subject subject) {
|
||||
if (subject == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean checkCached(final SimpleString dest, final String user, final CheckType checkType) {
|
||||
long now = System.currentTimeMillis();
|
||||
String validatedUser = "";
|
||||
Set<UserPrincipal> users = subject.getPrincipals(UserPrincipal.class);
|
||||
|
||||
// should only ever be 1 UserPrincipal
|
||||
for (UserPrincipal userPrincipal : users) {
|
||||
validatedUser = userPrincipal.getName();
|
||||
}
|
||||
return validatedUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
// public for testing purposes
|
||||
public void invalidateAuthorizationCache() {
|
||||
authorizationCache.invalidateAll();
|
||||
}
|
||||
|
||||
// public for testing purposes
|
||||
public void invalidateAuthenticationCache() {
|
||||
authenticationCache.invalidateAll();
|
||||
}
|
||||
|
||||
public long getAuthenticationCacheSize() {
|
||||
return authenticationCache.size();
|
||||
}
|
||||
|
||||
public long getAuthorizationCacheSize() {
|
||||
return authorizationCache.size();
|
||||
}
|
||||
|
||||
private boolean checkAuthorizationCache(final SimpleString dest, final String user, final CheckType checkType) {
|
||||
boolean granted = false;
|
||||
|
||||
if (now - lastCheck > invalidationInterval) {
|
||||
invalidateCache();
|
||||
|
||||
lastCheck = now;
|
||||
} else {
|
||||
ConcurrentHashSet<SimpleString> act = cache.get(user + "." + checkType.name());
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
public boolean authorize(final Subject subject,
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
public boolean authorize(Subject subject,
|
||||
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);
|
||||
CheckType checkType) {
|
||||
System.out.println("authorize(" + subject + ", " + roles + ", " + checkType + ")");
|
||||
return activeMQJAASSecurityManager.authorize(subject, roles, checkType);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -197,6 +197,32 @@ public class ActiveMQServerControlTest extends ManagementTestBase {
|
|||
Assert.assertTrue(serverControl.isActive());
|
||||
}
|
||||
|
||||
@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();
|
||||
|
|
|
@ -768,6 +768,16 @@ public class ActiveMQServerControlUsingCoreTest extends ActiveMQServerControlTes
|
|||
return 0;
|
||||
}
|
||||
|
||||
@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 {
|
||||
|
|
|
@ -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");
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue