Secure API Responses with Data Masking, Encryption, and Jackson Annotations
This article explains practical methods to protect sensitive API data in Spring Boot applications, covering encryption, masking, access control, logging, HTTPS, and database security, and demonstrates three implementation approaches: SQL‑level masking, field‑level encryption, and custom Jackson serialization using annotations and a contextual serializer.
Environment: SpringBoot2.6.12
API interface data masking is an important method for protecting sensitive data. It replaces sensitive information with meaningless or disguised data during transmission and storage to prevent unauthorized access and leakage.
Data Encryption : Use encryption algorithms (e.g., AES, RSA) to encrypt sensitive data so that intercepted data cannot be decrypted.
Data Masking : Replace sensitive data with meaningless or disguised values, such as random phone numbers or fake names.
Access Control : Apply authentication and authorization (e.g., JWT, OAuth) to restrict API access to verified users.
Logging : Record detailed logs of API access and usage for rapid incident response.
Data Transmission Security : Use HTTPS to secure data in transit.
Data Storage Security : Store sensitive data in encrypted databases and enforce ACLs.
In projects, some API fields should not be transmitted in plain text. The following three methods can be used:
Database‑Level Processing
Processing data in SQL queries is generally inefficient and not recommended. Example:
<code>SELECT CONCAT(LEFT(idNo, 6), '********', RIGHT(idNo, 4)) AS idNo
FROM users WHERE id = 7;</code>Query result:
Data Encryption Processing
This approach encrypts the entire field using symmetric encryption or hash algorithms during write or read operations. Example:
<code>import java.sql.*;
public class JdbcSensitiveDataProcess {
public static void main(String[] args) {
try {
// Connect to database
Connection conn = DataSourceUtils.getConnection();
// Create Statement
Statement stmt = conn.createStatement();
// Execute query
ResultSet rs = stmt.executeQuery("SELECT id, name, encrypted_data FROM t_xxx");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String encryptedData = rs.getString("encrypted_data");
// Decrypt the data
String decryptedData = decryptData(encryptedData);
System.out.println("ID: " + id + ", Name: " + name + ", Decrypted Data: " + decryptedData);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// Close resources
}
}
// Example decryption method (implementation depends on the algorithm used)
private static String decryptData(String encryptedData) {
// Decryption logic...
return encryptedData; // placeholder
}
}
</code>JSON Serialization Processing
During JSON serialization, sensitive fields can be masked or encrypted. The following demonstrates a custom Jackson annotation approach.
JSON Serialization Masking
Using Jackson, define a custom annotation to handle sensitive fields during object serialization.
<code>@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = SensitiveSerializer.class)
public @interface Sensitive {
/** Regular expression */
String pattern() default "";
/** Which regex group to replace with mask */
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})";
/** Key */
String KEY = "(\\w+)";
}
}
</code>The annotation provides pattern, group, and mask attributes for flexible masking.
<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) {
Pattern pa = 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>This serializer reads the @Sensitive annotation on a field and applies the defined masking logic.
<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>Result:
idNo is masked according to the specified rule.
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.