CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-security--spring-security-cas

Spring Security support for Apereo's Central Authentication Service (CAS) enabling Single Sign-On authentication

Pending
Overview
Eval results
Files

ticket-caching.mddocs/

Ticket Caching

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.

Capabilities

Stateless Ticket Cache Interface

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);
}

Null Stateless Ticket Cache

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();
}

Spring Cache-Based Ticket Cache

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()));
    }
}

Cache Configuration Examples

Redis-Based Caching

@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);
    }
}

Hazelcast-Based Caching

@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);
    }
}

Ehcache-Based Caching

@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);
    }
}

Cache Integration with Authentication Provider

@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;
}

Cache Considerations

Cache Key Strategy

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"

Cache Expiration

  • Short TTL: Tickets should expire quickly to maintain security
  • Ticket Lifetime: Should not exceed CAS server ticket expiration
  • Recommended: 5-10 minutes maximum
  • Security: Shorter expiration reduces risk of ticket replay

Serialization Requirements

Cached objects must be serializable:

// CasAuthenticationToken implements Serializable
public class CasAuthenticationToken extends AbstractAuthenticationToken implements Serializable {
    private static final long serialVersionUID = 1L;
    // ...
}

Performance Benefits

  • Reduced Network Calls: Avoids repeated CAS server validation
  • Improved Response Time: Local cache access vs. network round-trip
  • Server Load Reduction: Decreases load on CAS server
  • Scalability: Better performance in high-traffic scenarios

Cache Monitoring

@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());
    }
}

Security Considerations

  • Cache Isolation: Separate cache namespace for CAS tickets
  • Access Control: Restrict cache access to authentication components
  • Encryption: Consider encrypting cached authentication tokens
  • Audit Trail: Log cache operations for security monitoring
  • Cleanup: Ensure proper cache cleanup on logout or session invalidation

Best Practices

  1. Use Distributed Cache: For clustered applications
  2. Short Expiration: Keep TTL under 10 minutes
  3. Monitor Performance: Track cache hit/miss ratios
  4. Secure Configuration: Encrypt sensitive cache data
  5. Proper Sizing: Configure appropriate cache size limits
  6. Exception Handling: Handle cache failures gracefully

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-security--spring-security-cas

docs

authentication.md

configuration.md

index.md

json-serialization.md

ticket-caching.md

user-details.md

web-integration.md

tile.json