package com.baiyue.common.audit; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; /** * 审计数据脱敏器,在记录请求快照前递归屏蔽密码、令牌、密钥等敏感字段。 */ public class SensitiveDataSanitizer { private static final Set SENSITIVE = Set.of("password", "passwordhash", "token", "accesstoken", "refreshtoken", "authorization", "secret", "idcard", "bankcard", "mobile", "phone", "email"); private final ObjectMapper mapper; private final int maxLength; public SensitiveDataSanitizer(ObjectMapper mapper, int maxLength) { this.mapper = mapper; this.maxLength = maxLength; } public String sanitize(Object value) { if (value == null) return null; try { JsonNode root = mapper.valueToTree(value); redact(root); String json = mapper.writeValueAsString(root); return json.length() <= maxLength ? json : json.substring(0, maxLength) + "...[truncated]"; } catch (IllegalArgumentException | JsonProcessingException e) { return "\"[unserializable]\""; } } private void redact(JsonNode node) { if (node == null) return; if (node.isObject()) { Iterator> fields = node.fields(); while (fields.hasNext()) { Map.Entry field = fields.next(); String normalized = field.getKey().replace("_", "").toLowerCase(Locale.ROOT); if (SENSITIVE.stream().anyMatch(normalized::contains)) { ((ObjectNode) node).put(field.getKey(), "******"); } else redact(field.getValue()); } } else if (node.isArray()) node.forEach(this::redact); } }