1
0

Part 7: the safe pattern - compile-time subtype registry

This commit is contained in:
2026-08-04 17:34:05 +00:00
parent 4ea4845baf
commit 39bdf83f6e

View File

@@ -0,0 +1,49 @@
package com.ankurm.jackson3.part7security;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import tools.jackson.databind.json.JsonMapper;
/**
* Post: Jackson Security Best Practices — https://ankurm.com/jackson-security-best-practices/
* Section: "Use @JsonTypeInfo Instead of Default Typing"
*
* The safe pattern: the permitted types are fixed at compile time, so no JSON payload
* can introduce a class name of its own.
*/
public class H01SafePolymorphismByAnnotation {
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = EmailNotification.class, name = "email"),
@JsonSubTypes.Type(value = SmsNotification.class, name = "sms")
})
public abstract static class Notification { }
public static class EmailNotification extends Notification {
public String recipientEmail;
@Override public String toString() { return "EmailNotification[" + recipientEmail + "]"; }
}
public static class SmsNotification extends Notification {
public String recipientPhone;
@Override public String toString() { return "SmsNotification[" + recipientPhone + "]"; }
}
public static void main(String[] args) {
JsonMapper mapper = JsonMapper.builder().build();
System.out.println("email : " + mapper.readValue(
"{\"type\":\"email\",\"recipientEmail\":\"a@example.com\"}", Notification.class));
System.out.println("sms : " + mapper.readValue(
"{\"type\":\"sms\",\"recipientPhone\":\"+441234567890\"}", Notification.class));
// A class name supplied by an attacker is not a registered logical name.
try {
mapper.readValue("{\"type\":\"com.malicious.Gadget\"}", Notification.class);
System.out.println("attack: UNEXPECTEDLY ACCEPTED");
} catch (Exception e) {
System.out.println("attack: rejected with " + e.getClass().getSimpleName());
}
}
}