Apereo CAS Core Utilities - A comprehensive utility library providing functional programming constructs, encryption utilities, configuration helpers, and core infrastructure components for the Central Authentication Service framework
—
Object serialization utilities with support for various formats, compression, encryption, and Jackson integration for secure and efficient data persistence and transmission.
Core utility class providing comprehensive serialization operations with support for encryption and encoding.
@UtilityClass
public class SerializationUtils {
// Basic serialization operations
public static byte[] serialize(Serializable object);
public static void serialize(Serializable object, OutputStream outputStream);
// Basic deserialization operations
public static <T> T deserialize(byte[] inBytes, Class<T> clazz);
public static <T> T deserialize(InputStream inputStream, Class<T> clazz);
// Encrypted serialization operations
public static byte[] serializeAndEncodeObject(EncodableCipher cipher,
Serializable object,
Object[] parameters);
public static byte[] serializeAndEncodeObject(EncodableCipher cipher,
Serializable object);
// Encrypted deserialization operations
public static <T extends Serializable> T decodeAndDeserializeObject(
byte[] object,
Class<T> type,
DecodableCipher cipher,
Object[] parameters
);
public static <T extends Serializable> T decodeAndDeserializeObject(
byte[] object,
Class<T> type,
DecodableCipher cipher
);
// Validation and safety operations
public static <T extends Serializable> T deserializeAndCheckObject(
byte[] object,
Class<T> type
);
}Basic serialization operations:
@Service
public class CacheService {
public void storeUserSession(String sessionId, UserSession session) {
try {
// Serialize user session
byte[] serialized = SerializationUtils.serialize(session);
// Store in cache/database
cacheManager.put(sessionId, serialized);
} catch (Exception e) {
log.error("Failed to serialize user session", e);
throw new SerializationException("Session storage failed", e);
}
}
public Optional<UserSession> retrieveUserSession(String sessionId) {
try {
byte[] data = cacheManager.get(sessionId);
if (data != null) {
UserSession session = SerializationUtils.deserialize(data, UserSession.class);
return Optional.of(session);
}
return Optional.empty();
} catch (Exception e) {
log.error("Failed to deserialize user session", e);
return Optional.empty();
}
}
public void persistToFile(Serializable object, Path filePath) {
try (FileOutputStream fos = new FileOutputStream(filePath.toFile())) {
SerializationUtils.serialize(object, fos);
} catch (Exception e) {
log.error("Failed to persist object to file", e);
throw new SerializationException("File persistence failed", e);
}
}
public <T> Optional<T> loadFromFile(Path filePath, Class<T> type) {
try (FileInputStream fis = new FileInputStream(filePath.toFile())) {
T object = SerializationUtils.deserialize(fis, type);
return Optional.of(object);
} catch (Exception e) {
log.error("Failed to load object from file", e);
return Optional.empty();
}
}
}Encrypted serialization for sensitive data:
@Service
public class SecureDataService {
private final CipherExecutor<Serializable, byte[]> cipher;
public SecureDataService(CipherExecutor<Serializable, byte[]> cipher) {
this.cipher = cipher;
}
public void storeSecureData(String key, SensitiveData data) {
try {
// Serialize and encrypt in one operation
byte[] encrypted = SerializationUtils.serializeAndEncodeObject(cipher, data);
// Store encrypted data
secureStorage.store(key, encrypted);
log.info("Securely stored data for key: {}", key);
} catch (Exception e) {
log.error("Failed to store secure data", e);
throw new SecurityException("Secure storage failed", e);
}
}
public Optional<SensitiveData> retrieveSecureData(String key) {
try {
byte[] encrypted = secureStorage.retrieve(key);
if (encrypted != null) {
// Decrypt and deserialize in one operation
SensitiveData data = SerializationUtils.decodeAndDeserializeObject(
encrypted,
SensitiveData.class,
cipher
);
return Optional.of(data);
}
return Optional.empty();
} catch (Exception e) {
log.error("Failed to retrieve secure data", e);
return Optional.empty();
}
}
public void storeWithParameters(String key, ConfigurableData data, String[] params) {
try {
// Use cipher parameters for additional security context
Object[] cipherParams = Arrays.stream(params)
.map(String::getBytes)
.toArray();
byte[] encrypted = SerializationUtils.serializeAndEncodeObject(
cipher,
data,
cipherParams
);
secureStorage.store(key, encrypted);
} catch (Exception e) {
log.error("Failed to store configurable data", e);
throw new SecurityException("Parameterized storage failed", e);
}
}
}Safe deserialization with validation:
@Component
public class SafeDeserializationService {
private final Set<Class<?>> allowedClasses;
public SafeDeserializationService() {
// Whitelist of allowed classes for deserialization
this.allowedClasses = Set.of(
UserSession.class,
AuthenticationToken.class,
ServiceTicket.class,
CacheEntry.class
);
}
public <T extends Serializable> Optional<T> safeDeserialize(byte[] data, Class<T> type) {
// Validate class is allowed
if (!isAllowedClass(type)) {
log.warn("Attempted to deserialize disallowed class: {}", type.getName());
return Optional.empty();
}
try {
// Use safe deserialization with validation
T object = SerializationUtils.deserializeAndCheckObject(data, type);
// Additional validation
if (isValidObject(object)) {
return Optional.of(object);
} else {
log.warn("Deserialized object failed validation: {}", type.getName());
return Optional.empty();
}
} catch (Exception e) {
log.error("Safe deserialization failed for type: {}", type.getName(), e);
return Optional.empty();
}
}
private boolean isAllowedClass(Class<?> clazz) {
return allowedClasses.contains(clazz) ||
allowedClasses.stream().anyMatch(allowed -> allowed.isAssignableFrom(clazz));
}
private boolean isValidObject(Object object) {
// Implement business logic validation
if (object instanceof UserSession session) {
return session.getUserId() != null && session.getCreatedAt() != null;
}
if (object instanceof AuthenticationToken token) {
return token.getToken() != null && token.getExpiresAt() > System.currentTimeMillis();
}
return true; // Default validation
}
}Abstract Jackson-based serializer providing customizable JSON serialization.
public abstract class BaseJacksonSerializer<T> {
// Jackson ObjectMapper instance
protected final ObjectMapper objectMapper;
// Constructor
protected BaseJacksonSerializer(ObjectMapper objectMapper);
// Serialization methods
public String serialize(T object);
public byte[] serializeToBytes(T object);
public void serializeToStream(T object, OutputStream outputStream);
// Deserialization methods
public T deserialize(String json);
public T deserialize(byte[] data);
public T deserialize(InputStream inputStream);
// Abstract methods for subclasses
protected abstract Class<T> getObjectType();
protected abstract void configureObjectMapper(ObjectMapper mapper);
}Factory for creating and configuring Jackson ObjectMapper instances with CAS-specific settings.
public class JacksonObjectMapperFactory {
// Factory methods
public static ObjectMapper builder();
public static ObjectMapper builder(boolean defaultTypingEnabled);
// Configuration methods
public static ObjectMapper configure(ObjectMapper mapper);
public static ObjectMapper configureForCas(ObjectMapper mapper);
// Specialized configurations
public static ObjectMapper createForTickets();
public static ObjectMapper createForServices();
public static ObjectMapper createForAuthentication();
// Feature configuration
public static ObjectMapper withFeature(ObjectMapper mapper,
SerializationFeature feature,
boolean enabled);
public static ObjectMapper withFeature(ObjectMapper mapper,
DeserializationFeature feature,
boolean enabled);
}Custom Jackson serializer implementation:
@Component
public class UserSessionSerializer extends BaseJacksonSerializer<UserSession> {
public UserSessionSerializer() {
super(JacksonObjectMapperFactory.builder());
}
@Override
protected Class<UserSession> getObjectType() {
return UserSession.class;
}
@Override
protected void configureObjectMapper(ObjectMapper mapper) {
// Custom configuration for user sessions
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// Custom date format
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
// Include type information for polymorphic handling
mapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
// Custom modules
mapper.registerModule(new JavaTimeModule());
mapper.registerModule(new CasJacksonModule());
}
}Factory usage for different contexts:
@Configuration
public class SerializationConfiguration {
@Bean("ticketObjectMapper")
public ObjectMapper ticketObjectMapper() {
ObjectMapper mapper = JacksonObjectMapperFactory.createForTickets();
// Additional ticket-specific configuration
mapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
mapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
return mapper;
}
@Bean("serviceObjectMapper")
public ObjectMapper serviceObjectMapper() {
ObjectMapper mapper = JacksonObjectMapperFactory.createForServices();
// Service registry specific settings
mapper.configure(SerializationFeature.INDENT_OUTPUT, true);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
@Bean("authenticationObjectMapper")
public ObjectMapper authenticationObjectMapper() {
ObjectMapper mapper = JacksonObjectMapperFactory.createForAuthentication();
// Security-focused configuration
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
mapper.configure(JsonParser.Feature.STRICT_DUPLICATE_DETECTION, true);
return mapper;
}
}Default serialization plan for component-based serialization strategies.
public class DefaultComponentSerializationPlan implements ComponentSerializationPlan {
// Serialization strategy configuration
private final Map<Class<?>, SerializationStrategy> strategies;
// Constructor
public DefaultComponentSerializationPlan();
public DefaultComponentSerializationPlan(Map<Class<?>, SerializationStrategy> strategies);
// Plan execution
@Override
public byte[] serialize(Object object);
@Override
public <T> T deserialize(byte[] data, Class<T> type);
// Strategy management
public void registerStrategy(Class<?> type, SerializationStrategy strategy);
public void removeStrategy(Class<?> type);
public SerializationStrategy getStrategy(Class<?> type);
}Component serialization plan:
@Configuration
public class SerializationPlanConfiguration {
@Bean
public ComponentSerializationPlan casSerializationPlan() {
Map<Class<?>, SerializationStrategy> strategies = new HashMap<>();
// Different strategies for different types
strategies.put(UserSession.class, new JacksonSerializationStrategy());
strategies.put(ServiceTicket.class, new CompactBinaryStrategy());
strategies.put(AuthenticationToken.class, new EncryptedJsonStrategy());
strategies.put(CacheEntry.class, new CompressedSerializationStrategy());
DefaultComponentSerializationPlan plan = new DefaultComponentSerializationPlan(strategies);
// Register additional strategies
plan.registerStrategy(RegisteredService.class, new ServiceJsonStrategy());
return plan;
}
}
@Service
public class ComponentSerializationService {
private final ComponentSerializationPlan serializationPlan;
public byte[] serializeComponent(Object component) {
return serializationPlan.serialize(component);
}
public <T> T deserializeComponent(byte[] data, Class<T> type) {
return serializationPlan.deserialize(data, type);
}
}Injectable value supplier for providing runtime values during Jackson deserialization.
public class JacksonInjectableValueSupplier extends InjectableValues {
// Value providers
private final Map<String, Supplier<Object>> valueSuppliers;
private final ApplicationContext applicationContext;
// Constructor
public JacksonInjectableValueSupplier(ApplicationContext applicationContext);
// Value injection
@Override
public Object findInjectableValue(Object valueId,
DeserializationContext ctxt,
BeanProperty forProperty,
Object beanInstance);
// Supplier registration
public void registerSupplier(String valueId, Supplier<Object> supplier);
public void registerBean(String valueId, Class<?> beanType);
public void registerConstant(String valueId, Object value);
}Injectable value configuration:
@Configuration
public class JacksonInjectableConfiguration {
@Bean
public JacksonInjectableValueSupplier injectableValueSupplier(ApplicationContext context) {
JacksonInjectableValueSupplier supplier = new JacksonInjectableValueSupplier(context);
// Register runtime suppliers
supplier.registerSupplier("currentTime", System::currentTimeMillis);
supplier.registerSupplier("serverId", () -> getServerId());
supplier.registerSupplier("environment", () -> getEnvironment());
// Register Spring beans for injection
supplier.registerBean("userService", UserService.class);
supplier.registerBean("cacheManager", CacheManager.class);
// Register constants
supplier.registerConstant("version", "7.2.4");
supplier.registerConstant("deployment", "production");
return supplier;
}
@Bean
public ObjectMapper injectableObjectMapper(JacksonInjectableValueSupplier supplier) {
ObjectMapper mapper = JacksonObjectMapperFactory.builder();
mapper.setInjectableValues(supplier);
return mapper;
}
}
// Usage in domain objects
public class AuditableEntity {
@JsonProperty
private String id;
@JacksonInject("currentTime")
private Long createdAt;
@JacksonInject("serverId")
private String serverId;
@JacksonInject("userService")
private transient UserService userService;
// getters/setters
}Pattern-based JSON deserializer for handling regular expressions and pattern objects.
public class PatternJsonDeserializer extends JsonDeserializer<Pattern> {
@Override
public Pattern deserialize(JsonParser parser,
DeserializationContext context) throws IOException;
// Pattern compilation options
public Pattern deserializeWithFlags(JsonParser parser,
DeserializationContext context,
int flags) throws IOException;
}@Service
@Slf4j
public class ComprehensiveSerializationService {
private final ObjectMapper jacksonMapper;
private final CipherExecutor<Serializable, byte[]> cipher;
private final ComponentSerializationPlan serializationPlan;
public ComprehensiveSerializationService(
@Qualifier("casObjectMapper") ObjectMapper jacksonMapper,
CipherExecutor<Serializable, byte[]> cipher,
ComponentSerializationPlan serializationPlan) {
this.jacksonMapper = jacksonMapper;
this.cipher = cipher;
this.serializationPlan = serializationPlan;
}
// JSON serialization for web APIs
public String toJson(Object object) {
try {
return jacksonMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.error("JSON serialization failed", e);
throw new SerializationException("JSON conversion failed", e);
}
}
public <T> T fromJson(String json, Class<T> type) {
try {
return jacksonMapper.readValue(json, type);
} catch (JsonProcessingException e) {
log.error("JSON deserialization failed", e);
throw new SerializationException("JSON parsing failed", e);
}
}
// Binary serialization for caching
public byte[] toBinary(Serializable object) {
return SerializationUtils.serialize(object);
}
public <T> T fromBinary(byte[] data, Class<T> type) {
return SerializationUtils.deserialize(data, type);
}
// Encrypted serialization for sensitive data
public byte[] toEncryptedBinary(Serializable object) {
return SerializationUtils.serializeAndEncodeObject(cipher, object);
}
public <T extends Serializable> T fromEncryptedBinary(byte[] data, Class<T> type) {
return SerializationUtils.decodeAndDeserializeObject(data, type, cipher);
}
// Component-based serialization
public byte[] serializeComponent(Object component) {
return serializationPlan.serialize(component);
}
public <T> T deserializeComponent(byte[] data, Class<T> type) {
return serializationPlan.deserialize(data, type);
}
// Hybrid operations combining multiple strategies
public StorageEntry createStorageEntry(String key, Object data, StorageOptions options) {
byte[] serializedData;
if (options.isEncrypted()) {
if (data instanceof Serializable serializable) {
serializedData = toEncryptedBinary(serializable);
} else {
// Convert to JSON first, then encrypt
String json = toJson(data);
serializedData = toEncryptedBinary(json);
}
} else if (options.useComponentSerialization()) {
serializedData = serializeComponent(data);
} else if (options.isJsonFormat()) {
String json = toJson(data);
serializedData = json.getBytes(StandardCharsets.UTF_8);
} else {
if (data instanceof Serializable serializable) {
serializedData = toBinary(serializable);
} else {
throw new IllegalArgumentException("Object must be Serializable for binary format");
}
}
return new StorageEntry(key, serializedData, options);
}
public <T> T retrieveFromStorageEntry(StorageEntry entry, Class<T> type) {
StorageOptions options = entry.getOptions();
byte[] data = entry.getData();
if (options.isEncrypted()) {
if (String.class.equals(type)) {
String decrypted = fromEncryptedBinary(data, String.class);
return jacksonMapper.convertValue(decrypted, type);
} else {
return fromEncryptedBinary(data, type.asSubclass(Serializable.class));
}
} else if (options.useComponentSerialization()) {
return deserializeComponent(data, type);
} else if (options.isJsonFormat()) {
String json = new String(data, StandardCharsets.UTF_8);
return fromJson(json, type);
} else {
return fromBinary(data, type);
}
}
}This serialization library provides comprehensive support for various serialization needs in CAS applications, from simple JSON conversion to encrypted binary storage with proper security and performance considerations.
Install with Tessl CLI
npx tessl i tessl/maven-org-apereo-cas--cas-server-core-util-api