How to Elegantly Implement Dynamic, Configurable, High‑Performance Data Desensitization in Spring Boot
The article explains why user privacy data must be masked, then walks through a Spring Boot solution that uses custom annotations, an aspect‑oriented filter, a contextual serializer and a flexible rule engine stored in Redis to achieve dynamic, high‑performance data desensitization without polluting business logic.
1. Background
In modern internet applications, protecting user privacy is critical. Sensitive fields such as name, phone number, ID card, address, etc., are encrypted in the database and must be masked before being returned to the front‑end. The requirement is that any field can be masked, and the masking rules must be configurable at runtime.
2. Implementation Idea
Rather than masking each field manually in controller methods, the solution adopts an aspect‑oriented approach. Fields that need masking are annotated with a custom @MaskField annotation. A global @ControllerAdvice is avoided because reflection on every request would hurt performance. Instead, a custom @JsonFormat -like annotation is used together with a Jackson JsonSerializer that reads the annotation and applies masking logic.
3. Core Components
3.1 Annotation definition
@Retention(RetentionPolicy.RUNTIME)
@JacksonAnnotationsInside
@JsonSerialize(using = MaskSerializer.class)
public @interface MaskField {
MaskEnum value();
}The annotation holds a single attribute value() that specifies the masking type defined in MaskEnum.
3.2 MaskEnum
public enum MaskEnum {
NAME, ID_CARD, MOBILE, ADDRESS, EMAIL, BANK_CARD, CUSTOM_FIELD
}3.3 Runtime context
A servlet filter AuthFilter runs after login authentication, reads the company‑specific masking configuration from Redis, and stores it in MaskContextHolder. If the cache misses, the filter falls back to a database query and updates Redis.
@Component
@Slf4j
public class AuthFilter implements Filter {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
String rule = stringRedisTemplate.opsForValue()
.get(KeyCache.ORG_DESENSITIZATION_SETTING + company.getId());
// parse rule, evaluate switches, set MaskContextHolder
chain.doFilter(request, response);
MaskContextHolder.clear();
}
}3.4 MaskSerializer
The serializer checks the thread‑local mask flag, obtains the list of DesensitizationRuleCreate objects, and applies the appropriate masking algorithm based on the enum value.
public class MaskSerializer<T> extends JsonSerializer<T> implements ContextualSerializer {
private MaskEnum type;
@Override
public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException {
if (!MaskContextHolder.getMask()) { gen.writeObject(value); return; }
List<DesensitizationRuleCreate> rules = MaskContextHolder.getMaskSetting().getRules();
// switch on type and call MaskUtils.commonMask(...)
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty prop) throws JsonMappingException {
MaskField mf = prop.getAnnotation(MaskField.class);
return mf != null ? new MaskSerializer<>(mf.value()) : prov.findValueSerializer(prop.getType(), prop);
}
}3.5 MaskUtils
Utility methods wrap Hutool’s DesensitizedUtil for common types and provide a generic commonMask method that implements the rule engine (type, scope, count, start, end) as described in the source.
public static String commonMask(String text, DesensitizationRuleCreate rule) {
if (StringUtils.isBlank(text) || Objects.isNull(rule)) return text;
// build masked string according to rule.scope, rule.type, etc.
return ms.toString();
}4. Usage Example
Annotate VO fields with @MaskField and let Jackson handle serialization.
@Data
public class CaseInfoVO extends Base {
@ApiModelProperty("姓名")
@MaskField(MaskEnum.NAME)
private String name;
@ApiModelProperty("身份证号码")
@MaskField(MaskEnum.ID_CARD)
private String idCard;
@ApiModelProperty("电话号码")
@MaskField(MaskEnum.MOBILE)
private String mobile;
@ApiModelProperty("自定义字段")
@MaskField(MaskEnum.CUSTOM_FIELD)
private Map<String, String> fields;
}When the endpoint returns the VO, the JSON output contains masked values such as:
{
"status":200,
"message":"success",
"data":{
"id":11592359,
"name":"郑**",
"idCard":"33010219601031****",
"mobile":"*******0030",
"fields":{...}
},
"success":true
}5. Summary
The guide demonstrates a complete, dynamic, and configurable data‑desensitization mechanism for Spring Boot applications. By leveraging custom annotations, a request‑scoped context populated from Redis, and a Jackson serializer that applies rule‑driven masking, the solution achieves low overhead, avoids business‑logic intrusion, and supports both built‑in and user‑defined fields.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
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.
