Spring Boot 敏感数据脱敏优雅实现方案

🎁 福利时间

如果你正在备战面试或者想要学习其他知识,给大家推荐一个宝藏知识库,作者整理了一些列 Java 程序员需要掌握的核心知识,有需要的自取不谢。

知识库地址:https://farerboy.com/


一、脱敏的重要性

在现代应用中,保护用户敏感数据是至关重要的。敏感数据包括但不限于:

  • 手机号
  • 身份证号
  • 银行卡号
  • 姓名
  • 邮箱地址
  • 密码

脱敏处理可以有效防止敏感信息泄露,保护用户隐私,同时满足合规要求(如GDPR、PCI DSS等)。

二、实现方案

2.1 基于注解的脱敏方案

核心注解
java 复制代码
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Sensitive {
    SensitiveType type();
    int prefix() default 3; // 保留前缀长度
    int suffix() default 4; // 保留后缀长度
    char mask() default '*'; // 掩码字符
}
脱敏类型枚举
java 复制代码
public enum SensitiveType {
    PHONE,      // 手机号
    ID_CARD,    // 身份证号
    BANK_CARD,  // 银行卡号
    NAME,       // 姓名
    EMAIL,      // 邮箱
    PASSWORD    // 密码
}
脱敏处理器接口
java 复制代码
public interface SensitiveHandler {
    String handle(String value, int prefix, int suffix, char mask);
}
脱敏处理器实现
java 复制代码
import org.springframework.stereotype.Component;

@Component
public class SensitiveHandlerImpl implements SensitiveHandler {
    @Override
    public String handle(String value, int prefix, int suffix, char mask) {
        if (value == null || value.length() <= prefix + suffix) {
            return value;
        }
        
        StringBuilder result = new StringBuilder();
        result.append(value.substring(0, prefix));
        
        int maskLength = value.length() - prefix - suffix;
        for (int i = 0; i < maskLength; i++) {
            result.append(mask);
        }
        
        result.append(value.substring(value.length() - suffix));
        return result.toString();
    }
}
脱敏工具类
java 复制代码
import java.lang.reflect.Field;

public class SensitiveUtils {
    public static <T> T desensitize(T object, SensitiveHandler handler) {
        if (object == null) {
            return null;
        }
        
        Class<?> clazz = object.getClass();
        Field[] fields = clazz.getDeclaredFields();
        
        for (Field field : fields) {
            if (field.isAnnotationPresent(Sensitive.class)) {
                Sensitive sensitive = field.getAnnotation(Sensitive.class);
                field.setAccessible(true);
                
                try {
                    Object value = field.get(object);
                    if (value instanceof String) {
                        String maskedValue = handler.handle(
                            (String) value,
                            sensitive.prefix(),
                            sensitive.suffix(),
                            sensitive.mask()
                        );
                        field.set(object, maskedValue);
                    }
                } catch (IllegalAccessException e) {
                    // 处理异常
                }
            }
        }
        
        return object;
    }
}

2.2 基于AOP的脱敏方案

切面类
java 复制代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class SensitiveDataAspect {
    @Autowired
    private SensitiveHandler sensitiveHandler;
    
    @Around("execution(* com.example.controller.*.*(..))")
    public Object handleSensitiveData(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = joinPoint.proceed();
        return SensitiveUtils.desensitize(result, sensitiveHandler);
    }
}

2.3 基于拦截器的脱敏方案

拦截器类
java 复制代码
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class SensitiveDataInterceptor implements HandlerInterceptor {
    @Autowired
    private SensitiveHandler sensitiveHandler;
    @Autowired
    private ObjectMapper objectMapper;
    
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 处理完成后的操作
    }
}
响应包装类
java 复制代码
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

public class SensitiveResponseWrapper extends HttpServletResponseWrapper {
    private ByteArrayOutputStream buffer;
    private ServletOutputStream out;
    private PrintWriter writer;
    
    public SensitiveResponseWrapper(HttpServletResponse response) {
        super(response);
        buffer = new ByteArrayOutputStream();
        out = new WrappedOutputStream(buffer);
        writer = new PrintWriter(buffer);
    }
    
    @Override
    public ServletOutputStream getOutputStream() throws IOException {
        return out;
    }
    
    @Override
    public PrintWriter getWriter() throws IOException {
        return writer;
    }
    
    @Override
    public void flushBuffer() throws IOException {
        if (out != null) {
            out.flush();
        }
        if (writer != null) {
            writer.flush();
        }
    }
    
    @Override
    public void reset() {
        buffer.reset();
    }
    
    public byte[] getContent() throws IOException {
        flushBuffer();
        return buffer.toByteArray();
    }
    
    private class WrappedOutputStream extends ServletOutputStream {
        private ByteArrayOutputStream bos;
        
        public WrappedOutputStream(ByteArrayOutputStream bos) {
            this.bos = bos;
        }
        
        @Override
        public void write(int b) throws IOException {
            bos.write(b);
        }
        
        @Override
        public boolean isReady() {
            return true;
        }
        
        @Override
        public void setWriteListener(WriteListener listener) {
        }
    }
}
拦截器配置
java 复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new SensitiveDataInterceptor())
                .addPathPatterns("/**");
    }
}

三、使用示例

3.1 基于注解的使用

java 复制代码
public class User {
    private Long id;
    
    @Sensitive(type = SensitiveType.NAME)
    private String name;
    
    @Sensitive(type = SensitiveType.PHONE)
    private String phone;
    
    @Sensitive(type = SensitiveType.ID_CARD)
    private String idCard;
    
    @Sensitive(type = SensitiveType.BANK_CARD)
    private String bankCard;
    
    @Sensitive(type = SensitiveType.EMAIL)
    private String email;
    
    // getters and setters
}

// 使用方式
@Autowired
private SensitiveHandler sensitiveHandler;

public User getUser() {
    User user = userService.getUser();
    return SensitiveUtils.desensitize(user, sensitiveHandler);
}

3.2 基于AOP的使用

java 复制代码
@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    private UserService userService;
    
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        return userService.getUser(id);
        // 返回结果会被AOP自动脱敏
    }
}

3.3 基于拦截器的使用

java 复制代码
// 只需配置拦截器,所有HTTP响应都会被自动脱敏

四、方案比较

方案 优点 缺点 适用场景
基于注解 灵活性高,可针对具体字段定制 需要手动调用脱敏方法 单个对象脱敏
基于AOP 自动处理,无需手动调用 只能拦截特定方法 控制器方法返回值脱敏
基于拦截器 全局处理,覆盖所有HTTP响应 实现复杂,性能开销较大 全局HTTP响应脱敏

五、扩展与优化

5.1 自定义脱敏规则

java 复制代码
// 实现自定义脱敏处理器
public class CustomSensitiveHandler implements SensitiveHandler {
    @Override
    public String handle(String value, int prefix, int suffix, char mask) {
        // 实现自定义脱敏逻辑
        return "自定义脱敏结果";
    }
}

5.2 脱敏类型扩展

java 复制代码
public enum SensitiveType {
    // 原有类型
    PHONE, ID_CARD, BANK_CARD, NAME, EMAIL, PASSWORD,
    // 新增类型
    ADDRESS, // 地址
    BIRTHDAY // 生日
}

5.3 性能优化

  • 使用缓存存储反射结果
  • 批量处理脱敏操作
  • 只对需要脱敏的字段进行处理

六、总结

本文介绍了三种Spring Boot应用中敏感数据脱敏的实现方案:

  1. 基于注解的脱敏方案:通过自定义注解和反射实现,灵活性高,可针对具体字段定制脱敏规则。

  2. 基于AOP的脱敏方案:通过切面拦截方法执行,自动对返回结果进行脱敏处理,无需手动调用。

  3. 基于拦截器的脱敏方案:通过拦截HTTP响应,对所有响应数据进行脱敏处理,实现全局脱敏。

每种方案都有其适用场景,开发者可以根据具体需求选择合适的方案,或结合使用多种方案以达到最佳效果。

通过这些方案,可以有效地保护用户敏感数据,防止信息泄露,同时满足合规要求,为应用提供更加安全可靠的用户体验。


相关推荐
J2虾虾1 小时前
Spring Boot实现发邮件功能
java·spring boot·spring
8Qi81 小时前
LeetCode 295:数据流的中位数(Median Finder)—— Java 题解 ✅
java·算法·leetcode·优先队列··中位数
competes1 小时前
数据查询方式最左匹配原则
java·大数据·前端·人工智能·windows
❀͜͡傀儡师1 小时前
告别脚手架:用 JBang 打通 Java、Kotlin、Python 的脚本化开发
java·python·kotlin·jbang
学计算机的计算基1 小时前
MySQL 锁体系全解:从 MDL 到间隙锁,一次讲透
java·数据库·笔记·python·mysql
jjjava2.01 小时前
全面拆解 Java 锁:分类辨析 + 底层原理精讲
java·开发语言
曹牧1 小时前
Java:import NeverUsed
java·开发语言·log4j
之歆1 小时前
在 IntelliJ IDEA 里复刻 Cursor 式内联审查:架构复盘-从放弃到拾起:如何用 LineStatusTracker 拯救一个烂掉的项目
java·架构·intellij-idea
jeffer_liu1 小时前
Spring AI 生产级实战-结构化输出
java·人工智能·后端·spring·大模型