| | |
| | | import java.util.Iterator; |
| | | import java.util.Map; |
| | | |
| | | /** Ensures every normal management JSON response uses the same privacy rules. */ |
| | | /** 确保所有常规管理端 JSON 响应均遵循统一的隐私保护规则。 */ |
| | | @ControllerAdvice |
| | | public class SensitiveDataMaskingAdvice implements ResponseBodyAdvice<Object> { |
| | | private final HttpServletRequest request; |
| | | public SensitiveDataMaskingAdvice(HttpServletRequest request) { this.request = request; } |
| | | |
| | | @Override public boolean supports(MethodParameter type, Class<? extends HttpMessageConverter<?>> converter) { |
| | | // Only the explicit, admin-only and audited endpoint returns the source value. |
| | | // 仅明确限定、仅管理员可访问且已审计的接口返回原始数据。 |
| | | return !request.getRequestURI().startsWith("/system/sensitive/"); |
| | | } |
| | | @Override public Object beforeBodyWrite(Object body, MethodParameter type, MediaType mediaType, |
| | |
| | | } |
| | | private void mask(Object value, IdentityHashMap<Object, Boolean> seen) { |
| | | if (value == null || value instanceof String || value.getClass().isEnum() || seen.put(value, Boolean.TRUE) != null) return; |
| | | // Reactive streams and their internal queues are runtime infrastructure, not |
| | | // response DTOs. In particular, Reactor's MpscLinkedQueue implements |
| | | // Iterable but deliberately throws UnsupportedOperationException from |
| | | // iterator(). Do not introspect it (or any other reactive infrastructure). |
| | | // 响应式流及其内部队列属于运行时基础设施,并非响应 DTO。特别是 Reactor 的 |
| | | // MpscLinkedQueue 虽实现了 Iterable,却会在 iterator() 中主动抛出 |
| | | // UnsupportedOperationException;不得反射遍历它或其他响应式基础设施。 |
| | | if (isReactiveInfrastructure(value.getClass())) return; |
| | | // UnixPath implements Iterable. Its iterator creates a new Path for every |
| | | // segment, so identity-based cycle detection cannot stop the recursion. |
| | | // UnixPath 实现了 Iterable,其迭代器会为每个路径段创建新的 Path 对象, |
| | | // 因此基于对象标识的循环检测无法终止递归。 |
| | | if (value instanceof Path) return; |
| | | if (value instanceof Map) { |
| | | for (Object item : ((Map<?, ?>) value).entrySet()) { |
| | |
| | | } |
| | | |
| | | /** |
| | | * Some third-party containers expose Iterable only for API compatibility and |
| | | * intentionally do not support iteration. A response privacy filter must never |
| | | * turn such an implementation detail into a 500 response. |
| | | * 部分第三方容器仅为 API 兼容而实现 Iterable,并不支持实际迭代。 |
| | | * 响应隐私过滤器不得因这类实现细节导致接口返回 500 错误。 |
| | | */ |
| | | private void maskIterable(Iterable<?> values, IdentityHashMap<Object, Boolean> seen) { |
| | | try { |
| | | Iterator<?> iterator = values.iterator(); |
| | | while (iterator.hasNext()) mask(iterator.next(), seen); |
| | | } catch (UnsupportedOperationException ignored) { |
| | | // There is no safely accessible element to mask. |
| | | // 没有可安全访问并进行脱敏的元素。 |
| | | } |
| | | } |
| | | |