Skip to content

Commit

Permalink
fix: overlay du code source pour correction du metaspace leak avec le…
Browse files Browse the repository at this point in the history
… redis ticket registry
  • Loading branch information
nathancailbourdin committed Jun 14, 2024
1 parent 4c3c568 commit 3d5f61c
Show file tree
Hide file tree
Showing 3 changed files with 812 additions and 0 deletions.
8 changes: 8 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -282,5 +282,13 @@ dependencies {
implementation "org.apereo.cas:cas-server-support-oauth-webflow"
implementation "org.apereo.cas:cas-server-support-jmx"
implementation "org.apereo.cas:cas-server-support-pac4j-webflow"
// Compilation modifs redis
compileOnly "org.apereo.cas:cas-server-core-util-api"
compileOnly "org.apereo.cas:cas-server-support-redis-core"
compileOnly "org.apereo.cas:cas-server-core-authentication-api"
compileOnly "org.apereo.cas:cas-server-core-tickets-api"
compileOnly "org.springframework.data:spring-data-redis"
compileOnly "org.springframework.integration:spring-integration-redis"
compileOnly "com.redis:lettucemod"

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
package org.apereo.cas.config;

import org.apereo.cas.authentication.CasSSLContext;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.features.CasFeatureModule;
import org.apereo.cas.configuration.support.Beans;
import org.apereo.cas.redis.core.CasRedisTemplate;
import org.apereo.cas.redis.core.RedisObjectFactory;
import org.apereo.cas.ticket.Ticket;
import org.apereo.cas.ticket.TicketCatalog;
import org.apereo.cas.ticket.registry.CachedTicketExpirationPolicy;
import org.apereo.cas.ticket.registry.DefaultTicketRegistry;
import org.apereo.cas.ticket.registry.RedisCompositeKey;
import org.apereo.cas.ticket.registry.RedisTicketDocument;
import org.apereo.cas.ticket.registry.RedisTicketRegistry;
import org.apereo.cas.ticket.registry.RedisTicketRegistryCacheEndpoint;
import org.apereo.cas.ticket.registry.TicketRegistry;
import org.apereo.cas.ticket.registry.key.PrincipalRedisKeyGenerator;
import org.apereo.cas.ticket.registry.key.RedisKeyGeneratorFactory;
import org.apereo.cas.ticket.registry.key.TicketRedisKeyGenerator;
import org.apereo.cas.ticket.registry.pub.DefaultRedisTicketRegistryMessagePublisher;
import org.apereo.cas.ticket.registry.pub.RedisTicketRegistryMessagePublisher;
import org.apereo.cas.ticket.registry.sub.DefaultRedisTicketRegistryMessageListener;
import org.apereo.cas.ticket.serialization.TicketSerializationManager;
import org.apereo.cas.util.CoreTicketUtils;
import org.apereo.cas.util.PublisherIdentifier;
import org.apereo.cas.util.function.FunctionUtils;
import org.apereo.cas.util.lock.DefaultLockRepository;
import org.apereo.cas.util.lock.LockRepository;
import org.apereo.cas.util.spring.beans.BeanCondition;
import org.apereo.cas.util.spring.beans.BeanSupplier;
import org.apereo.cas.util.spring.boot.ConditionalOnFeatureEnabled;

import com.github.benmanes.caffeine.cache.Cache;
import com.redis.lettucemod.api.sync.RedisModulesCommands;
import lombok.val;
import org.jooq.lambda.Unchecked;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.convert.KeyspaceConfiguration;
import org.springframework.data.redis.core.convert.MappingConfiguration;
import org.springframework.data.redis.core.index.IndexConfiguration;
import org.springframework.data.redis.core.mapping.RedisMappingContext;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.Topic;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.integration.redis.util.RedisLockRegistry;
import org.springframework.integration.support.locks.LockRegistry;

import java.util.Optional;

/**
* This is {@link RedisTicketRegistryConfiguration}.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
@EnableConfigurationProperties(CasConfigurationProperties.class)
@ConditionalOnFeatureEnabled(feature = CasFeatureModule.FeatureCatalog.TicketRegistry, module = "redis")
@AutoConfiguration
public class RedisTicketRegistryConfiguration {

private static final BeanCondition CONDITION = BeanCondition.on("cas.ticket.registry.redis.enabled").isTrue().evenIfMissing();

@Configuration(value = "RedisTicketRegistryCoreConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
@Lazy(false)
public static class RedisTicketRegistryCoreConfiguration {

@Bean
@ConditionalOnAvailableEndpoint
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public RedisTicketRegistryCacheEndpoint redisTicketRegistryCacheEndpoint(
final CasConfigurationProperties casProperties,
@Qualifier(TicketRegistry.BEAN_NAME)
final TicketRegistry ticketRegistry,
@Qualifier("redisTicketRegistryCache")
final Cache<String, Ticket> redisTicketRegistryCache) {
return new RedisTicketRegistryCacheEndpoint(casProperties, ticketRegistry, redisTicketRegistryCache);
}

@Bean
@ConditionalOnMissingBean(name = "redisTicketRegistryMessageTopic")
public Topic redisTicketRegistryMessageTopic() {
return new ChannelTopic(RedisCompositeKey.REDIS_TICKET_REGISTRY_MESSAGE_TOPIC);
}

@Bean
@ConditionalOnMissingBean(name = "redisTicketRegistryMessageListenerContainer")
public RedisMessageListenerContainer redisTicketRegistryMessageListenerContainer(
@Qualifier("redisTicketRegistryMessageTopic")
final ChannelTopic redisTicketRegistryMessageTopic,
@Qualifier("redisTicketRegistryMessageListener")
final MessageListener redisTicketRegistryMessageListener,
@Qualifier("redisTicketConnectionFactory")
final RedisConnectionFactory redisTicketConnectionFactory) {
val container = new RedisMessageListenerContainer();
container.setConnectionFactory(redisTicketConnectionFactory);
container.addMessageListener(redisTicketRegistryMessageListener, redisTicketRegistryMessageTopic);
return container;
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "redisTicketRegistryMessageIdentifier")
public PublisherIdentifier redisTicketRegistryMessageIdentifier(
final CasConfigurationProperties casProperties) {
val bean = new PublisherIdentifier();
val redis = casProperties.getTicket().getRegistry().getRedis();
FunctionUtils.doIfNotBlank(redis.getQueueIdentifier(), bean::setId);
return bean;
}

@Bean
@ConditionalOnMissingBean(name = "redisTicketRegistryMessageListener")
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@Lazy(false)
public MessageListener redisTicketRegistryMessageListener(
@Qualifier("redisTicketRegistryMessageIdentifier")
final PublisherIdentifier redisTicketRegistryMessageIdentifier,
@Qualifier("redisTicketRegistryCache")
final Cache<String, Ticket> redisTicketRegistryCache) {
val adapter = new MessageListenerAdapter(
new DefaultRedisTicketRegistryMessageListener(redisTicketRegistryMessageIdentifier, redisTicketRegistryCache));
adapter.setSerializer(new JdkSerializationRedisSerializer());
return adapter;
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "redisTicketRegistryMessagePublisher")
public RedisTicketRegistryMessagePublisher redisTicketRegistryMessagePublisher(
@Qualifier("redisTicketRegistryMessageIdentifier")
final PublisherIdentifier redisTicketRegistryMessageIdentifier,
@Qualifier("ticketRedisTemplate")
final CasRedisTemplate<String, RedisTicketDocument> ticketRedisTemplate) {
return new DefaultRedisTicketRegistryMessagePublisher(ticketRedisTemplate, redisTicketRegistryMessageIdentifier);
}

@ConditionalOnMissingBean(name = "redisTicketConnectionFactory")
@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public RedisConnectionFactory redisTicketConnectionFactory(
final ConfigurableApplicationContext applicationContext,
final CasConfigurationProperties casProperties,
@Qualifier(CasSSLContext.BEAN_NAME)
final CasSSLContext casSslContext) {
return BeanSupplier.of(RedisConnectionFactory.class)
.when(CONDITION.given(applicationContext.getEnvironment()))
.supply(Unchecked.supplier(() -> {
val redis = casProperties.getTicket().getRegistry().getRedis();
return RedisObjectFactory.newRedisConnectionFactory(redis, casSslContext);
}))
.otherwiseProxy()
.get();
}

@Bean(name = {"redisTemplate", "ticketRedisTemplate"})
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "ticketRedisTemplate")
public CasRedisTemplate<String, RedisTicketDocument> ticketRedisTemplate(
final ConfigurableApplicationContext applicationContext,
@Qualifier("redisTicketConnectionFactory")
final RedisConnectionFactory redisTicketConnectionFactory) {
return BeanSupplier.of(CasRedisTemplate.class)
.when(CONDITION.given(applicationContext.getEnvironment()))
.supply(() -> RedisObjectFactory.newRedisTemplate(redisTicketConnectionFactory))
.otherwiseProxy()
.get();
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "sessionsRedisTemplate")
public CasRedisTemplate<String, String> sessionsRedisTemplate(
final ConfigurableApplicationContext applicationContext,
@Qualifier("redisTicketConnectionFactory")
final RedisConnectionFactory redisTicketConnectionFactory) {
return BeanSupplier.of(CasRedisTemplate.class)
.when(CONDITION.given(applicationContext.getEnvironment()))
.supply(() -> RedisObjectFactory.newRedisTemplate(redisTicketConnectionFactory))
.otherwiseProxy()
.get();
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "redisTicketRegistryCache")
public Cache<String, Ticket> redisTicketRegistryCache(final CasConfigurationProperties casProperties) {
val redis = casProperties.getTicket().getRegistry().getRedis();
return Beans.newCache(redis.getCache(), new CachedTicketExpirationPolicy());
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public RedisTicketRegistry.CasRedisTemplates casRedisTemplates(
@Qualifier("ticketRedisTemplate")
final CasRedisTemplate<String, RedisTicketDocument> ticketRedisTemplate,
@Qualifier("sessionsRedisTemplate")
final CasRedisTemplate<String, String> sessionsRedisTemplate) {
return new RedisTicketRegistry.CasRedisTemplates(ticketRedisTemplate, sessionsRedisTemplate);
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "redisKeyGeneratorFactory")
public RedisKeyGeneratorFactory redisKeyGeneratorFactory() {
val factory = new RedisKeyGeneratorFactory();
factory.registerRedisKeyGenerators(new TicketRedisKeyGenerator(), new PrincipalRedisKeyGenerator());
return factory;
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
@ConditionalOnMissingBean(name = "redisMappingContext")
public RedisMappingContext redisMappingContext() {
return new RedisMappingContext(new MappingConfiguration(new IndexConfiguration(), new KeyspaceConfiguration()));
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public TicketRegistry ticketRegistry(
@Qualifier(CasSSLContext.BEAN_NAME)
final CasSSLContext casSslContext,
@Qualifier("redisKeyGeneratorFactory")
final RedisKeyGeneratorFactory redisKeyGeneratorFactory,
@Qualifier("casRedisTemplates")
final RedisTicketRegistry.CasRedisTemplates casRedisTemplates,
@Qualifier(TicketCatalog.BEAN_NAME)
final TicketCatalog ticketCatalog,
@Qualifier(TicketSerializationManager.BEAN_NAME)
final TicketSerializationManager ticketSerializationManager,
@Qualifier("redisTicketRegistryCache")
final Cache<String, Ticket> redisTicketRegistryCache,
@Qualifier("redisTicketRegistryMessagePublisher")
final RedisTicketRegistryMessagePublisher redisTicketRegistryMessagePublisher,
final ConfigurableApplicationContext applicationContext,
final RedisMappingContext redisMappingContext,
final CasConfigurationProperties casProperties) {
return BeanSupplier.of(TicketRegistry.class)
.when(CONDITION.given(applicationContext.getEnvironment()))
.supply(Unchecked.supplier(() -> {
val redis = casProperties.getTicket().getRegistry().getRedis();
val cipher = CoreTicketUtils.newTicketRegistryCipherExecutor(redis.getCrypto(), "redis");
val searchCommands = redis.isEnableRedisSearch()
? RedisObjectFactory.newRedisModulesCommands(redis, casSslContext)
: Optional.<RedisModulesCommands>empty();
return new RedisTicketRegistry(cipher, ticketSerializationManager, ticketCatalog,
casRedisTemplates, redisTicketRegistryCache, redisTicketRegistryMessagePublisher,
searchCommands, redisKeyGeneratorFactory, redisMappingContext, casProperties);
}))
.otherwise(() -> new DefaultTicketRegistry(ticketSerializationManager, ticketCatalog))
.get();
}
}

@Configuration(value = "RedisTicketRegistryLockingConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
@ConditionalOnFeatureEnabled(feature = CasFeatureModule.FeatureCatalog.TicketRegistryLocking, module = "redis")
public static class RedisTicketRegistryLockingConfiguration {
private static final BeanCondition CONDITION_LOCKING =
BeanCondition.on("cas.ticket.registry.core.enable-locking").isTrue().evenIfMissing();

@Bean(destroyMethod = "destroy")
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public LockRegistry casTicketRegistryRedisLockRegistry(
final ConfigurableApplicationContext applicationContext,
@Qualifier("redisTicketConnectionFactory")
final RedisConnectionFactory redisTicketConnectionFactory) {
return BeanSupplier.of(LockRegistry.class)
.when(CONDITION_LOCKING.given(applicationContext.getEnvironment()))
.supply(() -> {
val registryKey = "cas-" + RedisLockRegistry.class.getSimpleName();
return new RedisLockRegistry(redisTicketConnectionFactory, registryKey);
})
.otherwiseProxy()
.get();
}

@Bean
@RefreshScope(proxyMode = ScopedProxyMode.DEFAULT)
public LockRepository casTicketRegistryLockRepository(
final ConfigurableApplicationContext applicationContext,
@Qualifier("casTicketRegistryRedisLockRegistry")
final LockRegistry casTicketRegistryRedisLockRegistry) {
return BeanSupplier.of(LockRepository.class)
.when(CONDITION_LOCKING.given(applicationContext.getEnvironment()))
.supply(() -> new DefaultLockRepository(casTicketRegistryRedisLockRegistry))
.otherwise(LockRepository::noOp)
.get();
}
}
}
Loading

0 comments on commit 3d5f61c

Please sign in to comment.