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.

Shepherd Advanced Notes
Shepherd Advanced Notes
Shepherd Advanced Notes
How to Elegantly Implement Dynamic, Configurable, High‑Performance Data Desensitization in Spring Boot

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javadynamic-configurationSpring Bootcustom-annotationjacksondata-desensitization
Shepherd Advanced Notes
Written by

Shepherd Advanced Notes

Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.