Implementing Data Masking
In modern application development, protecting personally identifiable information (PII) is a critical requirement. A clean way to handle this in a Spring Boot environment is by leveraging custom Jackson annotations. This approach allows us to mask data during the JSON serialization process without adding heavy third-party dependencies.
Custom Serializers
We define specific logic for different data types by extending JsonSerializer. Below are examples for masking phone numbers, usernames, and email addresses.
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
public class PhoneMaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null || value.length() < 3) {
gen.writeString(value);
return;
}
// Keep first two and last two digits, mask the rest
String masked = value.replaceAll("(?<=^..).(?=.*..$)", "*");
gen.writeString(masked);
}
}
public class UserNameMaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null || value.length() <= 1) {
gen.writeString(value);
return;
}
// Mask everything except the first character
String masked = value.replaceAll("(?<=^.).*", "*");
gen.writeString(masked);
}
}
public class EmailMaskingSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (value == null || !value.contains("@")) {
gen.writeString(value);
return;
}
// Mask characters in the local part of the email, keeping the first char after @
String masked = value.replaceAll("(?<=^.|(?<=@.).)([^@])(.*)(?=@|$)", "*$3");
gen.writeString(masked);
}
}
Applying Annotations to DTOs
Once the serializers are defined, apply them directly to the fields in your entity or DTO classes using @JsonSerialize.
public class UserProfileDTO {
@JsonSerialize(using = UserNameMaskingSerializer.class)
private String displayName;
@JsonSerialize(using = PhoneMaskingSerializer.class)
private String mobileNumber;
// Getters and Setters
}
Internal Processing Masking
The annotation approach works automatical when Spring serializes the response. However, if you need to mask data for internal logging or processing before sending it out, you can manually trigger the serialization cycle using ObjectMapper.
List<UserProfileDTO> userData = fetchUserDataFromDatabase();
// Convert to JSON and back to apply the masking logic defined in annotations
ObjectMapper mapper = new ObjectMapper();
String jsonOutput = mapper.writeValueAsString(userData);
List<UserProfileDTO> maskedData = mapper.readValue(jsonOutput,
new com.fasterxml.jackson.core.type.TypeReference<List<UserProfileDTO>>() {});
// Now maskedData contains the desensitized information for internal use