spring-projects/spring-security · error · IllegalArgumentException
The class with {id} and name of {className} is not in the al
Error message
The class with {id} and name of {className} is not in the allowlist. If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. If the serialization is only done by a trusted source, you can also enable default typing. See https://github.com/spring-projects/spring-security/issues/4370 for details What it means
SecurityJackson2Modules validates polymorphic deserialization targets against an allowlist of registered Spring Security classes before instantiating them. When Jackson's default typing resolves a type id to a class that was not explicitly enabled via SecurityJackson2Modules.enableDefaultTyping or a mixin, typeFromId rejects it with this IllegalArgumentException. This is a deliberate deserialization-safety guard introduced after spring-security#4370 to prevent gadget-class attacks through untrusted JSON.
Source
Thrown at core/src/main/java/org/springframework/security/jackson2/SecurityJackson2Modules.java:299
@Override
public JavaType typeFromId(DatabindContext context, String id) throws IOException {
DeserializationConfig config = (DeserializationConfig) context.getConfig();
JavaType result = this.delegate.typeFromId(context, id);
String className = result.getRawClass().getName();
if (isInAllowlist(className)) {
return result;
}
boolean isExplicitMixin = config.findMixInClassFor(result.getRawClass()) != null;
if (isExplicitMixin) {
return result;
}
JacksonAnnotation jacksonAnnotation = AnnotationUtils.findAnnotation(result.getRawClass(),
JacksonAnnotation.class);
if (jacksonAnnotation != null) {
return result;
}
throw new IllegalArgumentException("The class with " + id + " and name of " + className
+ " is not in the allowlist. "
+ "If you believe this class is safe to deserialize, please provide an explicit mapping using Jackson annotations or by providing a Mixin. "
+ "If the serialization is only done by a trusted source, you can also enable default typing. "
+ "See https://github.com/spring-projects/spring-security/issues/4370 for details");
}
private boolean isInAllowlist(String id) {
return ALLOWLIST_CLASS_NAMES.contains(id);
}
@Override
public String getDescForKnownTypeIds() {
return this.delegate.getDescForKnownTypeIds();
}
@Override
public JsonTypeInfo.Id getMechanism() {
return this.delegate.getMechanism();View on GitHub (pinned to 96852e8860)
Solutions
- Register the class's mixin with SecurityJackson2Modules (mapper.registerMixins or SecurityJackson2Modules mixins map) so its type id resolves explicitly
- Use SecurityJackson2Modules.enableDefaultTyping(mapper, DefaultTyping.NON_FINAL, As.PROPERTY) on the ObjectMapper rather than generic activateDefaultTyping
- Prefer deserializing into known Spring Security types; for custom types, subclass and add a Jackson mixin declaring @JsonTypeInfo and @JsonDeserialize
- If payloads are only produced by trusted code, explicitly opt into default typing as the message suggests, and document the trust boundary
Example fix
// before ObjectMapper mapper = new ObjectMapper(); mapper.activateDefaultTyping(...); // class not allowlisted -> IllegalArgumentException // after ObjectMapper mapper = new ObjectMapper(); SecurityJackson2Modules.enableDefaultTyping(mapper); mapper.registerMixins(MyCustomToken.class, MyCustomTokenMixin.class);
Defensive patterns
Strategy: validation
Validate before calling
Class<?> raw = null;
try { raw = mapper.getTypeFactory().findClass(typeId); } catch (ClassNotFoundException e) { /* reject */ }
boolean allowlisted = raw != null && (SecurityJackson2Modules.isWellKnownReturnValueType(raw)
|| registeredMixins.containsKey(raw));
if (!allowlisted) { throw new IllegalArgumentException("Type not allowlisted: " + typeId); } Type guard
static boolean isAllowlisted(Class<?> clazz) {
return clazz != null && AnnotationUtils.findAnnotation(clazz, JacksonAnnotation.class) != null;
} Try / catch
try {
return mapper.readValue(json, Object.class);
} catch (IllegalArgumentException e) {
if (e.getMessage() != null && e.getMessage().contains("is not in the allowlist")) {
// log and reject payload; register missing mixin if legitimate
}
throw e;
} Prevention
- Always build deserializing ObjectMappers via SecurityJackson2Modules.enableDefaultTyping, never raw activateDefaultTyping
- Register a mixin for every custom UserDetails/Authentication class you serialize
- Test round-trip serialization/deserialization of all cached security objects in CI
- Never deserialize security JSON from untrusted sources; prefer binary/session storage for trusted contexts
When it happens
Trigger: Calling ObjectMapper.readValue (or the ObjectMapper from SecurityJackson2Modules) on JSON containing @class / type id fields naming classes not covered by the allowlist; deserializing payloads serialized with custom UserDetails/Auth implementations without registering their mixins; enabling default typing manually instead of via SecurityJackson2Modules.enableDefaultTyping.
Common situations: Spring Session / Spring Security OAuth cache deserialization after upgrading Spring Security and adding new class fields; round-tripping custom Authentication or UserDetails objects through Redis or HTTP session JSON; copying an ObjectMapper from another service that allowlists different classes.
Understand the failure class
Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.
Related errors
- Expression was null but expected boolean result '{expression
- Failed to evaluate expression '{expressionString}'
- Invalid Client Registration: + fieldName
- An error occurred reading the OpenID Client Registration: ${
- An error occurred reading the OpenID Provider Configuration:
AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10).
Data as JSON: /api/errors/ba4c33c4c6e32691.
Report an issue: GitHub.