Masking Sensitive Fields in SpringBoot APIs: A Step-by-Step Guide
Learn three practical methods to protect sensitive API response data in SpringBoot 2.4.12—complete with SQL examples, custom Jackson annotations, and a reusable serializer to automatically mask fields like ID numbers during JSON output.
Environment: SpringBoot 2.4.12
Overview
In some API interfaces the returned fields should not be transmitted in plain text. This article presents three ways to handle such sensitive data.
Database‑level processing (masking in SQL)
Data encryption (symmetric encryption or hash)
JSON serialization processing (masking/encrypting during JSON generation)
Database‑level Masking
Masking can be done directly in the SQL query, though it is not efficient and rarely used.
<code>SELECT CONCAT(LEFT(idNo, 6), '********', RIGHT(idNo, 4)) AS idNo FROM users WHERE id = 7;</code>Result:
Data Encryption
This method encrypts the entire field using symmetric encryption or a hash algorithm.
JSON Serialization Masking
When generating the JSON string, sensitive information can be masked or encrypted. The following demonstrates a custom Jackson annotation and serializer.
Custom Annotation
<code>@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
/** Regular expression */
String pattern() default "";
/** Which group of the regex to replace */
int group() default 0;
/** Mask character */
String mask() default "*";
interface Pattern {
/** ID card */
String ID = "(\\w{5})(\\w+)(\\w{3})";
/** Phone */
String PHONE = "(\\w){3}(\\w+)(\\w{2})";
/** Secret */
String KEY = "(\\w+)";
}
}</code>Serializer Implementation
<code>public class SensitiveSerializer extends JsonSerializer<String> implements ContextualSerializer {
private Sensitive sensitive;
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
String val = value;
if (sensitive != null) {
String pattern = sensitive.pattern();
int groupIndex = sensitive.group();
String mask = sensitive.mask();
if (pattern.length() > 0) {
java.util.regex.Pattern pa = java.util.regex.Pattern.compile(pattern);
Matcher matcher = pa.matcher(value);
if (matcher.matches()) {
String group = matcher.group(groupIndex);
if (mask.length() > 0 && group.length() > 0) {
val = val.replace(group, String.join("", Collections.nCopies(group.length(), mask)));
}
}
}
}
gen.writeObject(val);
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
sensitive = property.getAnnotation(Sensitive.class);
return this;
}
}</code>Model Example
<code>public class Users {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+10")
private Date birthday;
private Integer age;
private String name;
// Apply the custom annotation to the field that needs masking
@Sensitive(pattern = Sensitive.Pattern.ID)
private String idNo;
}</code>Running result (ID number masked according to the rule):
Done!
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.