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

json-serialization.mddocs/

JSON Serialization

Jackson module for serializing CAS authentication tokens and related objects in distributed session scenarios. This module enables proper JSON serialization and deserialization of CAS authentication objects for session management in clustered applications.

Capabilities

CAS Jackson2 Module

Jackson module that provides JSON serialization support for CAS authentication types, enabling distributed session management and caching scenarios.

/**
 * Jackson module for JSON serialization of CAS authentication objects.
 * Registers mixins for proper serialization of CAS types in distributed environments.
 */
public class CasJackson2Module extends SimpleModule {
    
    /**
     * Creates CAS Jackson2 module with default configuration.
     * Automatically registers mixins for CAS authentication types.
     */
    public CasJackson2Module();
    
    /**
     * Sets up the module by registering mixins for CAS types.
     * Called automatically by Jackson during module registration.
     * @param context Jackson setup context for module configuration
     */
    public void setupModule(SetupContext context);
}

Usage Example:

@Configuration
public class JacksonConfig {
    
    @Bean
    @Primary
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new CasJackson2Module());
        return mapper;
    }
}

Jackson Mixin Classes

The module includes several Jackson mixin classes that handle the serialization complexity of CAS objects:

CAS Authentication Token Mixin

Mixin for deserializing CasAuthenticationToken objects using Jackson.

/**
 * Mixin class for CasAuthenticationToken deserialization.
 * Uses the private constructor designed for Jackson deserialization.
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, 
                isGetterVisibility = JsonAutoDetect.Visibility.NONE,
                getterVisibility = JsonAutoDetect.Visibility.NONE, 
                creatorVisibility = JsonAutoDetect.Visibility.ANY)
@JsonIgnoreProperties(ignoreUnknown = true)
class CasAuthenticationTokenMixin {
    
    /**
     * Mixin constructor for deserializing CasAuthenticationToken.
     * @param keyHash hashCode of the provider key for token validation
     * @param principal the authenticated principal (usually username)  
     * @param credentials the service/proxy ticket ID from CAS
     * @param authorities granted authorities for the user
     * @param userDetails detailed user information
     * @param assertion CAS assertion containing user attributes
     */
    @JsonCreator
    CasAuthenticationTokenMixin(
        @JsonProperty("keyHash") Integer keyHash,
        @JsonProperty("principal") Object principal,
        @JsonProperty("credentials") Object credentials,
        @JsonProperty("authorities") Collection<? extends GrantedAuthority> authorities,
        @JsonProperty("userDetails") UserDetails userDetails,
        @JsonProperty("assertion") Assertion assertion);
}

Assertion Implementation Mixin

Mixin for deserializing CAS AssertionImpl objects from the Apereo CAS client.

/**
 * Mixin for deserializing AssertionImpl from org.apereo.cas.client.validation package.
 * Handles CAS assertion objects containing authentication details and user attributes.
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY, 
                getterVisibility = JsonAutoDetect.Visibility.NONE,
                isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
class AssertionImplMixin {
    
    /**
     * Mixin constructor for deserializing AssertionImpl.
     * @param principal the Principal associated with the assertion
     * @param validFromDate when the assertion becomes valid
     * @param validUntilDate when the assertion expires
     * @param authenticationDate when the user was authenticated
     * @param attributes key/value pairs for assertion attributes
     */
    @JsonCreator
    AssertionImplMixin(
        @JsonProperty("principal") AttributePrincipal principal,
        @JsonProperty("validFromDate") Date validFromDate,
        @JsonProperty("validUntilDate") Date validUntilDate,
        @JsonProperty("authenticationDate") Date authenticationDate,
        @JsonProperty("attributes") Map<String, Object> attributes);
}

Attribute Principal Implementation Mixin

Mixin for deserializing CAS AttributePrincipalImpl objects from the Apereo CAS client.

/**
 * Mixin for deserializing AttributePrincipalImpl from org.apereo.cas.client.authentication package.
 * Handles CAS principal objects containing user identity and attributes.
 */
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY,
                getterVisibility = JsonAutoDetect.Visibility.NONE,
                isGetterVisibility = JsonAutoDetect.Visibility.NONE)
@JsonIgnoreProperties(ignoreUnknown = true)
class AttributePrincipalImplMixin {
    
    /**
     * Mixin constructor for deserializing AttributePrincipalImpl.
     * @param name unique identifier for the principal (username)
     * @param attributes key/value pairs for user attributes
     * @param proxyGrantingTicket ticket for obtaining proxy tickets
     * @param proxyRetriever callback implementation for CAS server communication
     */
    @JsonCreator
    AttributePrincipalImplMixin(
        @JsonProperty("name") String name,
        @JsonProperty("attributes") Map<String, Object> attributes,
        @JsonProperty("proxyGrantingTicket") String proxyGrantingTicket,
        @JsonProperty("proxyRetriever") ProxyRetriever proxyRetriever);
}

Serialization Support

The module provides serialization support for the following CAS types:

  • CasAuthenticationToken: Complete authentication token with user details and CAS assertion
  • AssertionImpl: CAS server assertion containing authentication details and attributes
  • AttributePrincipalImpl: User principal with attributes from CAS server

Usage Example

// Example: Serializing CasAuthenticationToken
CasAuthenticationToken token = new CasAuthenticationToken(
    "cas-key",
    "username",
    "ST-123456-abcdef",
    authorities,
    userDetails,
    assertion
);

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new CasJackson2Module());

// Serialize to JSON
String json = mapper.writeValueAsString(token);

// Deserialize from JSON
CasAuthenticationToken deserializedToken = mapper.readValue(json, CasAuthenticationToken.class);

Configuration Examples

Spring Boot Auto-Configuration

@SpringBootApplication
public class Application {
    
    @Bean
    public Module casJackson2Module() {
        return new CasJackson2Module();
    }
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Redis Session Configuration

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {
    
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new CasJackson2Module());
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}

Hazelcast Session Configuration

@Configuration
public class HazelcastSessionConfig {
    
    @Bean
    public HazelcastInstance hazelcastInstance() {
        Config config = new Config();
        
        // Configure serialization for CAS objects
        SerializationConfig serializationConfig = config.getSerializationConfig();
        serializationConfig.addSerializerConfig(
            new SerializerConfig()
                .setTypeClass(CasAuthenticationToken.class)
                .setImplementation(new JsonSerializer())
        );
        
        return Hazelcast.newHazelcastInstance(config);
    }
    
    public static class JsonSerializer implements StreamSerializer<CasAuthenticationToken> {
        private final ObjectMapper mapper;
        
        public JsonSerializer() {
            this.mapper = new ObjectMapper();
            this.mapper.registerModule(new CasJackson2Module());
        }
        
        @Override
        public void write(ObjectDataOutput out, CasAuthenticationToken token) throws IOException {
            String json = mapper.writeValueAsString(token);
            out.writeUTF(json);
        }
        
        @Override
        public CasAuthenticationToken read(ObjectDataInput in) throws IOException {
            String json = in.readUTF();
            return mapper.readValue(json, CasAuthenticationToken.class);
        }
        
        @Override
        public int getTypeId() {
            return 1;
        }
    }
}

Session Integration

HTTP Session Serialization

@Configuration
public class SessionConfig {
    
    @Bean
    public HttpSessionIdResolver httpSessionIdResolver() {
        return HeaderHttpSessionIdResolver.xAuthToken();
    }
    
    @Bean
    public SessionRepository<MapSession> sessionRepository() {
        MapSessionRepository repository = new MapSessionRepository(new ConcurrentHashMap<>());
        repository.setDefaultMaxInactiveInterval(Duration.ofMinutes(30));
        return repository;
    }
    
    @Bean
    public ObjectMapper sessionObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new CasJackson2Module());
        mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        return mapper;
    }
}

Security Context Serialization

@Configuration
public class SecurityContextConfig {
    
    @Bean
    public SecurityContextRepository securityContextRepository() {
        HttpSessionSecurityContextRepository repository = new HttpSessionSecurityContextRepository();
        repository.setAllowSessionCreation(true);
        return repository;
    }
    
    @Bean
    public ObjectMapper securityContextObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new CasJackson2Module());
        mapper.registerModule(new CoreJackson2Module());
        return mapper;
    }
}

Cache Integration

Spring Cache with JSON Serialization

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(casRedisSerializer()));
        
        return RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(config)
            .build();
    }
    
    @Bean
    public RedisSerializer<Object> casRedisSerializer() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.registerModule(new CasJackson2Module());
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}

Custom Serialization Configuration

Custom Mixin Registration

@Configuration
public class CustomJacksonConfig {
    
    @Bean
    public ObjectMapper customObjectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        
        // Register CAS module
        mapper.registerModule(new CasJackson2Module());
        
        // Add custom mixins for additional types
        mapper.addMixIn(CustomUserDetails.class, CustomUserDetailsMixin.class);
        
        // Configure additional settings
        mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
        
        return mapper;
    }
    
    @JsonIgnoreProperties(ignoreUnknown = true)
    public static abstract class CustomUserDetailsMixin {
        @JsonIgnore
        abstract String getPassword();
    }
}

JSON Structure Examples

Serialized CasAuthenticationToken

{
  "@class": "org.springframework.security.cas.authentication.CasAuthenticationToken",
  "keyHash": 123456789,
  "principal": "username",
  "credentials": "ST-123456-abcdefghijklmnop",
  "authorities": [
    {
      "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
      "authority": "ROLE_USER"
    }
  ],
  "userDetails": {
    "@class": "org.springframework.security.core.userdetails.User",
    "username": "username",
    "authorities": [...],
    "accountNonExpired": true,
    "accountNonLocked": true,
    "credentialsNonExpired": true,
    "enabled": true
  },
  "assertion": {
    "@class": "org.apereo.cas.client.validation.AssertionImpl",
    "principal": {
      "@class": "org.apereo.cas.client.authentication.AttributePrincipalImpl",
      "name": "username",
      "attributes": {
        "email": "user@example.com",
        "role": ["USER", "ADMIN"]
      }
    },
    "validFromDate": "2023-10-01T10:00:00.000+00:00",
    "validUntilDate": "2023-10-01T10:05:00.000+00:00",
    "authenticationAttributes": {}
  },
  "authenticated": true
}

Troubleshooting

Common Serialization Issues

// Issue: Circular reference during serialization
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class User implements UserDetails {
    // ...
}

// Issue: Missing default constructor
public class CustomUserDetails implements UserDetails {
    @JsonCreator
    public CustomUserDetails(@JsonProperty("username") String username) {
        this.username = username;
    }
}

// Issue: Incompatible Jackson versions
@Configuration
public class JacksonVersionConfig {
    @Bean
    public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
        return new Jackson2ObjectMapperBuilder()
            .modules(new CasJackson2Module())
            .featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    }
}

Security Considerations

  • Sensitive Data: Avoid serializing passwords or sensitive credentials
  • Type Safety: Use @JsonTypeInfo for secure polymorphic deserialization
  • Validation: Validate deserialized objects before use
  • Version Compatibility: Ensure Jackson version compatibility across services

Integration Notes

  • Register module before any CAS authentication occurs
  • Compatible with Spring Session and Spring Security Context persistence
  • Supports Redis, Hazelcast, and other distributed cache providers
  • Required for clustered applications using CAS authentication
  • Test serialization/deserialization in development environment

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