Spring Security support for Apereo's Central Authentication Service (CAS) enabling Single Sign-On authentication
—
Stateless ticket caching implementations for performance optimization in clustered and stateless authentication scenarios. These components provide caching strategies to avoid repeated CAS server validation calls for the same service tickets.
Core interface defining ticket caching operations for stateless CAS authentication scenarios.
/**
* Interface for caching CAS authentication tokens to avoid repeated server validation.
* Used in stateless authentication scenarios where tickets need to be validated multiple times.
*/
public interface StatelessTicketCache {
/**
* Gets a cached authentication token by service ticket ID.
* @param serviceTicket the CAS service ticket to look up
* @return cached CasAuthenticationToken or null if not found or expired
*/
CasAuthenticationToken getByTicketId(String serviceTicket);
/**
* Stores an authentication token in the cache.
* @param token the CasAuthenticationToken to cache
* @throws IllegalArgumentException if token is null or invalid
*/
void putTicketInCache(CasAuthenticationToken token);
/**
* Removes an authentication token from the cache.
* @param token the CasAuthenticationToken to remove
*/
void removeTicketFromCache(CasAuthenticationToken token);
/**
* Removes an authentication token from the cache by service ticket ID.
* @param serviceTicket the service ticket ID to remove
*/
void removeTicketFromCache(String serviceTicket);
}No-operation implementation that disables ticket caching entirely.
/**
* No-operation implementation of StatelessTicketCache that performs no caching.
* All operations are no-ops, effectively disabling ticket caching.
* Used when caching is not desired or in development environments.
*/
public final class NullStatelessTicketCache implements StatelessTicketCache {
/**
* Always returns null, indicating no cached ticket found.
* @param serviceTicket the service ticket (ignored)
* @return null always
*/
public CasAuthenticationToken getByTicketId(String serviceTicket);
/**
* No-operation method that does not store the token.
* @param token the token to cache (ignored)
*/
public void putTicketInCache(CasAuthenticationToken token);
/**
* No-operation method that does not remove anything.
* @param token the token to remove (ignored)
*/
public void removeTicketFromCache(CasAuthenticationToken token);
/**
* No-operation method that does not remove anything.
* @param serviceTicket the service ticket to remove (ignored)
*/
public void removeTicketFromCache(String serviceTicket);
}Usage Example:
@Bean
public StatelessTicketCache nullTicketCache() {
return new NullStatelessTicketCache();
}Production-ready implementation using Spring's cache abstraction for distributed caching support.
/**
* StatelessTicketCache implementation backed by Spring's Cache abstraction.
* Supports various cache providers (Redis, Hazelcast, Ehcache, etc.) through Spring Cache.
*/
public class SpringCacheBasedTicketCache implements StatelessTicketCache {
/**
* Creates cache implementation with specified Spring Cache instance.
* @param cache Spring Cache instance to use for storage
* @throws IllegalArgumentException if cache is null
*/
public SpringCacheBasedTicketCache(Cache cache);
/**
* Retrieves cached authentication token by service ticket ID.
* @param serviceTicket the service ticket to look up
* @return cached CasAuthenticationToken or null if not found
*/
public CasAuthenticationToken getByTicketId(String serviceTicket);
/**
* Stores authentication token in the cache using the service ticket as key.
* @param token the CasAuthenticationToken to cache (must not be null)
* @throws IllegalArgumentException if token or its credentials are null
*/
public void putTicketInCache(CasAuthenticationToken token);
/**
* Removes authentication token from cache.
* @param token the token to remove (uses token's credentials as key)
*/
public void removeTicketFromCache(CasAuthenticationToken token);
/**
* Removes authentication token from cache by service ticket ID.
* @param serviceTicket the service ticket ID to remove from cache
*/
public void removeTicketFromCache(String serviceTicket);
}Usage Example:
@Configuration
@EnableCaching
public class TicketCacheConfig {
@Bean
public CacheManager cacheManager() {
RedisCacheManager.Builder builder = RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory())
.cacheDefaults(cacheConfiguration());
return builder.build();
}
@Bean
public StatelessTicketCache springCacheBasedTicketCache() {
Cache cache = cacheManager().getCache("casTickets");
return new SpringCacheBasedTicketCache(cache);
}
private RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(5)) // Cache tickets for 5 minutes
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
}
}@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(
new RedisStandaloneConfiguration("localhost", 6379));
}
@Bean
public RedisCacheManager cacheManager() {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)) // 10-minute TTL
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(redisConnectionFactory())
.cacheDefaults(config)
.build();
}
@Bean
public StatelessTicketCache redisTicketCache() {
Cache cache = cacheManager().getCache("cas-tickets");
return new SpringCacheBasedTicketCache(cache);
}
}@Configuration
@EnableCaching
public class HazelcastCacheConfig {
@Bean
public HazelcastInstance hazelcastInstance() {
Config config = new Config();
config.getMapConfig("cas-tickets")
.setTimeToLiveSeconds(300) // 5-minute TTL
.setMaxIdleSeconds(300);
return Hazelcast.newHazelcastInstance(config);
}
@Bean
public CacheManager hazelcastCacheManager() {
return new HazelcastCacheManager(hazelcastInstance());
}
@Bean
public StatelessTicketCache hazelcastTicketCache() {
Cache cache = hazelcastCacheManager().getCache("cas-tickets");
return new SpringCacheBasedTicketCache(cache);
}
}@Configuration
@EnableCaching
public class EhcacheConfig {
@Bean
public CacheManager ehCacheManager() {
CachingProvider provider = Caching.getCachingProvider();
javax.cache.CacheManager cacheManager = provider.getCacheManager();
javax.cache.configuration.Configuration<String, CasAuthenticationToken> configuration =
Eh107Configuration.fromEhcacheCacheConfiguration(
CacheConfigurationBuilder
.newCacheConfigurationBuilder(String.class, CasAuthenticationToken.class,
ResourcePoolsBuilder.heap(1000))
.withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofMinutes(5)))
.build());
cacheManager.createCache("cas-tickets", configuration);
return new JCacheCacheManager(cacheManager);
}
@Bean
public StatelessTicketCache ehcacheTicketCache() {
Cache cache = ehCacheManager().getCache("cas-tickets");
return new SpringCacheBasedTicketCache(cache);
}
}@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(serviceProperties());
provider.setTicketValidator(ticketValidator());
provider.setUserDetailsService(userDetailsService());
provider.setKey("cas-authentication-provider");
// Set ticket cache for performance optimization
provider.setStatelessTicketCache(springCacheBasedTicketCache());
return provider;
}The cache uses the CAS service ticket as the key:
// Cache key format
String cacheKey = casAuthenticationToken.getCredentials().toString();
// Example cache key
"ST-123456-abcdefghijklmnop-cas-server.example.com"Cached objects must be serializable:
// CasAuthenticationToken implements Serializable
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
private static final long serialVersionUID = 1L;
// ...
}@Component
public class CacheMetrics {
@Autowired
private CacheManager cacheManager;
@EventListener
public void handleCacheHit(CacheHitEvent event) {
// Log or meter cache hits
log.debug("Cache hit for ticket: {}", event.getKey());
}
@EventListener
public void handleCacheMiss(CacheMissEvent event) {
// Log or meter cache misses
log.debug("Cache miss for ticket: {}", event.getKey());
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-springframework-security--spring-security-cas