or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

aot-native-support.mdauthentication-core.mdauthentication-events.mdauthentication-management.mdauthentication-tokens.mdauthorities.mdauthorization.mdcompromised-password.mdconcurrent-async.mddao-authentication.mdexpression-access-control.mdindex.mdjaas-authentication.mdjackson-serialization.mdmethod-security.mdobservation-metrics.mdone-time-tokens.mdprovisioning.mdsecurity-context.mdsession-management.mduser-details.md
tile.json

jackson-serialization.mddocs/

Jackson JSON Serialization Support

Overview

Spring Security provides comprehensive Jackson support for serializing and deserializing security-related objects. Spring Security 7.0+ uses Jackson 3, while earlier versions used Jackson 2 (now deprecated).

Key Information for Agents

Core Capabilities:

  • Jackson 3 module support via SecurityJacksonModules.getModules()
  • CoreJacksonModule registers mixins and type validation for core security types
  • Polymorphic type validation via BasicPolymorphicTypeValidator for secure deserialization
  • Mixin classes control serialization behavior (field visibility, type info, etc.)
  • Support for Authentication, SecurityContext, UserDetails, GrantedAuthority serialization

Key Interfaces and Classes:

  • SecurityJacksonModules - Utility: getModules(ClassLoader) returns list of modules
  • SecurityJacksonModule - Abstract base class for security modules
  • CoreJacksonModule - Module for core security types (Authentication, UserDetails, etc.)
  • Mixin classes: SimpleGrantedAuthorityMixin, UserMixin, UsernamePasswordAuthenticationTokenMixin, etc.

Default Behaviors:

  • Modules registered via SecurityJacksonModules.getModules() (discovers all available modules)
  • Type validation: Must configure BasicPolymorphicTypeValidator for secure deserialization
  • Default typing: Use @JsonTypeInfo on mixins (type info stored in JSON)
  • Field visibility: Mixins use JsonAutoDetect with field visibility (getters hidden)

Threading Model:

  • ObjectMapper is thread-safe (can be shared)
  • Serialization/deserialization is stateless

Lifecycle:

  • Modules registered once on ObjectMapper configuration
  • Type validator configured once per ObjectMapper

Exceptions:

  • Serialization errors: JsonProcessingException (handle in try/catch)
  • Deserialization errors: JsonMappingException (invalid JSON or type)

Edge Cases:

  • Type validation: Must whitelist allowed types (security critical - prevent type confusion attacks)
  • Custom types: Create custom module extending SecurityJacksonModule
  • Session storage: Serialize SecurityContext for Redis/DB session storage
  • Password serialization: Passwords serialized (consider encryption for sensitive storage)
  • Polymorphic types: Use @JsonTypeInfo for type information in JSON
  • Jackson 2 deprecated: Use Jackson 3 modules (Jackson 2 support removed in 7.0)

Jackson 3 Support (Current)

SecurityJacksonModules

Utility class for registering Spring Security Jackson modules.

public final class SecurityJacksonModules { .api }

Description: Factory for obtaining Jackson 3 modules for Spring Security types.

Static Methods:

public static List<JacksonModule> getModules(ClassLoader classLoader) { .api }
  • Returns all available Spring Security Jackson modules
  • Parameters: classLoader - ClassLoader for discovering modules
  • Returns: List of Jackson modules
public static List<JacksonModule> getModules(
    ClassLoader classLoader,
    BasicPolymorphicTypeValidator.Builder validatorBuilder) { .api }
  • Returns modules with custom polymorphic type validator
  • Parameters:
    • classLoader - ClassLoader for module discovery
    • validatorBuilder - Builder for configuring allowed types
  • Returns: List of Jackson modules

Package: org.springframework.security.jackson

Example:

@Configuration
public class JacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // Register Spring Security modules
        List<JacksonModule> modules =
            SecurityJacksonModules.getModules(getClass().getClassLoader());
        modules.forEach(mapper::registerModule);

        return mapper;
    }
}

SecurityJacksonModule

Abstract base class for Spring Security Jackson modules.

public abstract class SecurityJacksonModule extends JacksonModule { .api }

Description: Base class providing polymorphic type validation configuration.

Abstract Methods:

public abstract void configurePolymorphicTypeValidator(
    BasicPolymorphicTypeValidator.Builder builder) { .api }
  • Configures allowed types for polymorphic deserialization
  • Parameters: builder - Builder for configuring type validation

Package: org.springframework.security.jackson

CoreJacksonModule

Jackson module for core Spring Security types.

public class CoreJacksonModule extends SecurityJacksonModule { .api }

Description: Registers mixins and type validation for core security classes.

Supported Types:

  • SimpleGrantedAuthority
  • FactorGrantedAuthority
  • AnonymousAuthenticationToken
  • RememberMeAuthenticationToken
  • UsernamePasswordAuthenticationToken
  • TestingAuthenticationToken
  • User (UserDetails implementation)
  • SecurityContextImpl
  • BadCredentialsException
  • Java time types (Instant, Duration)
  • Java collections (unmodifiable variants)

Package: org.springframework.security.jackson

Example:

@Bean
public ObjectMapper securityObjectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new CoreJacksonModule());
    return mapper;
}

Serialization of Security Types

Authentication Serialization

Serialize and deserialize Authentication objects:

@Service
public class AuthenticationSerializationService {

    private final ObjectMapper objectMapper;

    public AuthenticationSerializationService() {
        this.objectMapper = new ObjectMapper();
        objectMapper.registerModule(new CoreJacksonModule());

        // Enable default typing for polymorphic types
        BasicPolymorphicTypeValidator validator =
            BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("org.springframework.security")
                .build();

        objectMapper.activateDefaultTyping(
            validator,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
    }

    public String serialize(Authentication authentication) throws Exception {
        return objectMapper.writeValueAsString(authentication);
    }

    public Authentication deserialize(String json) throws Exception {
        return objectMapper.readValue(json, Authentication.class);
    }
}

Example JSON:

{
  "@class": "org.springframework.security.authentication.UsernamePasswordAuthenticationToken",
  "principal": {
    "@class": "org.springframework.security.core.userdetails.User",
    "username": "user",
    "password": "{noop}password",
    "enabled": true,
    "accountNonExpired": true,
    "credentialsNonExpired": true,
    "accountNonLocked": true,
    "authorities": [
      "java.util.Collections$UnmodifiableSet",
      [
        {
          "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
          "authority": "ROLE_USER"
        }
      ]
    ]
  },
  "credentials": "{noop}password",
  "authenticated": true,
  "authorities": [
    "java.util.Collections$UnmodifiableSet",
    [
      {
        "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
        "authority": "ROLE_USER"
      }
    ]
  ]
}

SecurityContext Serialization

Serialize SecurityContext for session storage:

@Component
public class SecurityContextSerializer {

    private final ObjectMapper objectMapper;

    public SecurityContextSerializer() {
        this.objectMapper = JsonMapper.builder()
            .findAndAddModules()
            .build();

        // Register Spring Security modules
        SecurityJacksonModules.getModules(getClass().getClassLoader())
            .forEach(objectMapper::registerModule);

        // Configure type validation
        BasicPolymorphicTypeValidator validator =
            BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("org.springframework.security")
                .allowIfSubType("java.util")
                .allowIfSubType("java.time")
                .build();

        objectMapper.activateDefaultTyping(
            validator,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );
    }

    public byte[] serialize(SecurityContext context) {
        try {
            return objectMapper.writeValueAsBytes(context);
        } catch (Exception e) {
            throw new RuntimeException("Failed to serialize SecurityContext", e);
        }
    }

    public SecurityContext deserialize(byte[] data) {
        try {
            return objectMapper.readValue(data, SecurityContext.class);
        } catch (Exception e) {
            throw new RuntimeException(
                "Failed to deserialize SecurityContext", e
            );
        }
    }
}

UserDetails Serialization

Serialize UserDetails for caching or session storage:

public class UserDetailsSerializer {

    private final ObjectMapper objectMapper;

    public UserDetailsSerializer() {
        this.objectMapper = new ObjectMapper();
        objectMapper.registerModule(new CoreJacksonModule());
    }

    public String serialize(UserDetails userDetails) throws Exception {
        return objectMapper.writeValueAsString(userDetails);
    }

    public UserDetails deserialize(String json) throws Exception {
        return objectMapper.readValue(json, User.class);
    }
}

Example JSON:

{
  "@class": "org.springframework.security.core.userdetails.User",
  "username": "admin",
  "password": "{bcrypt}$2a$10$...",
  "enabled": true,
  "accountNonExpired": true,
  "credentialsNonExpired": true,
  "accountNonLocked": true,
  "authorities": [
    "java.util.Collections$UnmodifiableSet",
    [
      {
        "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
        "authority": "ROLE_ADMIN"
      },
      {
        "@class": "org.springframework.security.core.authority.SimpleGrantedAuthority",
        "authority": "ROLE_USER"
      }
    ]
  ]
}

Mixin Classes

Spring Security uses Jackson mixins to control serialization:

SimpleGrantedAuthorityMixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) { .api }
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE
)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class SimpleGrantedAuthorityMixin { .api }

Package: org.springframework.security.jackson

UserMixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) { .api }
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE
)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class UserMixin { .api }

Constructor Binding:

@JsonCreator
UserMixin(
    @JsonProperty("username") String username,
    @JsonProperty("password") String password,
    @JsonProperty("enabled") boolean enabled,
    @JsonProperty("accountNonExpired") boolean accountNonExpired,
    @JsonProperty("credentialsNonExpired") boolean credentialsNonExpired,
    @JsonProperty("accountNonLocked") boolean accountNonLocked,
    @JsonProperty("authorities") Set<? extends GrantedAuthority> authorities
)

UsernamePasswordAuthenticationTokenMixin

@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) { .api }
@JsonAutoDetect(
    fieldVisibility = JsonAutoDetect.Visibility.ANY,
    getterVisibility = JsonAutoDetect.Visibility.NONE,
    isGetterVisibility = JsonAutoDetect.Visibility.NONE
)
@JsonIgnoreProperties(ignoreUnknown = true)
abstract class UsernamePasswordAuthenticationTokenMixin { .api }

Custom Type Configuration

Configuring Polymorphic Type Validation

Customize allowed types for deserialization:

@Configuration
public class CustomJacksonConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // Register Security modules
        List<JacksonModule> modules =
            SecurityJacksonModules.getModules(getClass().getClassLoader());
        modules.forEach(mapper::registerModule);

        // Custom type validator
        BasicPolymorphicTypeValidator validator =
            BasicPolymorphicTypeValidator.builder()
                // Allow Spring Security types
                .allowIfSubType("org.springframework.security")
                // Allow custom types
                .allowIfSubType("com.example.security")
                // Allow specific classes
                .allowIfBaseType(Authentication.class)
                .allowIfBaseType(GrantedAuthority.class)
                // Allow Java standard types
                .allowIfSubType("java.util")
                .allowIfSubType("java.time")
                .build();

        mapper.activateDefaultTyping(
            validator,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );

        return mapper;
    }
}

Custom Security Module

Create custom Jackson module for application-specific types:

public class CustomSecurityJacksonModule extends SecurityJacksonModule {

    @Override
    public void setupModule(SetupContext context) {
        // Register custom mixins
        context.setMixInAnnotations(
            CustomAuthenticationToken.class,
            CustomAuthenticationTokenMixin.class
        );
        context.setMixInAnnotations(
            CustomUserDetails.class,
            CustomUserDetailsMixin.class
        );
    }

    @Override
    public void configurePolymorphicTypeValidator(
            BasicPolymorphicTypeValidator.Builder builder) {
        // Allow custom types
        builder.allowIfSubType("com.example.security");
    }
}

// Custom mixin
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
abstract class CustomAuthenticationTokenMixin {

    @JsonCreator
    CustomAuthenticationTokenMixin(
        @JsonProperty("principal") Object principal,
        @JsonProperty("credentials") Object credentials,
        @JsonProperty("customData") Map<String, Object> customData
    ) { }

    @JsonProperty("customData")
    abstract Map<String, Object> getCustomData();
}

// Registration
@Bean
public ObjectMapper objectMapper() {
    ObjectMapper mapper = new ObjectMapper();
    mapper.registerModule(new CoreJacksonModule());
    mapper.registerModule(new CustomSecurityJacksonModule());
    return mapper;
}

Redis Session Serialization

Configure Jackson for Redis session storage:

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer(objectMapper());
    }

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();

        // Register Spring Security modules
        SecurityJacksonModules.getModules(getClass().getClassLoader())
            .forEach(mapper::registerModule);

        // Configure type validation
        BasicPolymorphicTypeValidator validator =
            BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("org.springframework.security")
                .allowIfSubType("java.util")
                .allowIfSubType("java.time")
                .build();

        mapper.activateDefaultTyping(
            validator,
            ObjectMapper.DefaultTyping.NON_FINAL,
            JsonTypeInfo.As.PROPERTY
        );

        // Additional configuration
        mapper.configure(
            SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,
            false
        );
        mapper.registerModule(new JavaTimeModule());

        return mapper;
    }
}

Database Storage with JSON

Store authentication in database as JSON:

@Entity
@Table(name = "user_sessions")
public class UserSession {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "session_id", unique = true)
    private String sessionId;

    @Column(name = "authentication_json", columnDefinition = "TEXT")
    private String authenticationJson;

    @Column(name = "created_at")
    private Instant createdAt;

    @Column(name = "expires_at")
    private Instant expiresAt;

    // Getters and setters
}

@Service
public class SessionService {

    private final UserSessionRepository repository;
    private final ObjectMapper objectMapper;

    public SessionService(UserSessionRepository repository) {
        this.repository = repository;
        this.objectMapper = new ObjectMapper();
        objectMapper.registerModule(new CoreJacksonModule());
    }

    public void saveSession(String sessionId, Authentication authentication) {
        try {
            String json = objectMapper.writeValueAsString(authentication);

            UserSession session = new UserSession();
            session.setSessionId(sessionId);
            session.setAuthenticationJson(json);
            session.setCreatedAt(Instant.now());
            session.setExpiresAt(Instant.now().plus(Duration.ofHours(1)));

            repository.save(session);
        } catch (Exception e) {
            throw new RuntimeException("Failed to save session", e);
        }
    }

    public Authentication loadSession(String sessionId) {
        return repository.findBySessionId(sessionId)
            .map(session -> {
                try {
                    return objectMapper.readValue(
                        session.getAuthenticationJson(),
                        Authentication.class
                    );
                } catch (Exception e) {
                    throw new RuntimeException("Failed to load session", e);
                }
            })
            .orElse(null);
    }
}

Best Practices

  1. Always register modules - Include CoreJacksonModule and any custom modules
  2. Configure type validation - Use BasicPolymorphicTypeValidator for security
  3. Never trust external JSON - Validate and sanitize deserialized data
  4. Handle sensitive data - Consider encrypting serialized authentication
  5. Use @JsonIgnore - Exclude sensitive fields from serialization
  6. Version your JSON - Include version information for compatibility
  7. Test serialization - Thoroughly test round-trip serialization
  8. Configure ObjectMapper globally - Use single ObjectMapper instance
  9. Enable security features - Configure ObjectMapper with secure defaults

Security Considerations

Preventing Type Confusion Attacks

// Configure strict type validation
BasicPolymorphicTypeValidator validator =
    BasicPolymorphicTypeValidator.builder()
        // Whitelist specific packages
        .allowIfSubType("org.springframework.security")
        // Deny dangerous types
        .denyForExactBaseType(Object.class)
        .build();

mapper.activateDefaultTyping(
    validator,
    ObjectMapper.DefaultTyping.NON_FINAL,
    JsonTypeInfo.As.PROPERTY
);

Excluding Sensitive Information

public class SecureUserDetails extends User {

    @JsonIgnore
    private String sensitiveData;

    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;

    // Other fields and methods
}

Jackson 2 Support (Deprecated)

Spring Security 7.0 deprecated Jackson 2 support:

@Deprecated(since = "7.0") { .api }
public final class SecurityJackson2Modules {

    @Deprecated(since = "7.0")
    public static void enableDefaultTyping(ObjectMapper mapper) { .api }

    @Deprecated(since = "7.0")
    public static List<Module> getModules(ClassLoader classLoader) { .api }
}

@Deprecated(since = "7.0")
public class CoreJackson2Module extends SimpleModule { .api }

Migration Path:

// Old (deprecated)
ObjectMapper mapper = new ObjectMapper();
SecurityJackson2Modules.enableDefaultTyping(mapper);

// New (Jackson 3)
ObjectMapper mapper = new ObjectMapper();
SecurityJacksonModules.getModules(getClass().getClassLoader())
    .forEach(mapper::registerModule);

See Also

  • Security Context
  • Authentication
  • User Details
  • Jackson documentation
  • Spring Session documentation