SpringBoot + 日志脱敏 + 敏感字段自动过滤:开发环境可看,生产日志安全合规
相信很多小伙伴都有过这样的困扰:开发环境下为了调试方便,需要查看完整的用户信息,但在生产环境中这些敏感信息如果出现在日志里,就可能引发严重的安全问题。特别是像用户密码、手机号、身份证号这类敏感数据,一旦泄露后果不堪设想。
那么,有没有一种方式能让我们在开发环境保留完整信息便于调试,而在生产环境自动对敏感数据进行脱敏处理,确保日志安全合规呢?今天我就跟大家分享一套基于SpringBoot的智能日志脱敏方案。
为什么需要日志脱敏?
先来说说我们面临的挑战。在日常开发中,我们经常需要在日志中打印用户信息来调试问题,比如:
2023-10-20 10:30:15 [http-nio-8080-exec-2] INFO c.e.service.UserService - 用户登录成功,用户信息:{id=1, username=user123, password=123456, email=user@example.com, phone=13812345678}
这样的日志虽然方便调试,但同时也把用户的敏感信息暴露了出来。如果这些日志被恶意获取,就会造成严重的信息泄露。
日志脱敏的作用是:
- 保护用户隐私信息
- 符合数据安全法规要求
- 降低安全风险
- 便于日志审计
整体架构设计
我们的脱敏方案由以下几个组件构成:
- @SensitiveField注解:用于标记需要脱敏的字段
- LogSanitizationHandler:日志脱敏处理器,实现具体的脱敏逻辑
- LogSanitizationAspect:AOP切面,实现自动脱敏
- EnvironmentUtil:环境检测工具,根据环境决定脱敏策略
- 配置管理:灵活的配置选项,适应不同环境需求
让我们看看如何在SpringBoot中实现这套脱敏系统:
1. 创建敏感字段注解
首先定义一个注解来标记敏感字段:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SensitiveField {
/**
* 脱敏类型
*/
SensitiveType type() default SensitiveType.CUSTOM;
/**
* 自定义脱敏规则
*/
String maskPattern() default "***";
/**
* 脱敏类型枚举
*/
enum SensitiveType {
PASSWORD, // 密码
PHONE, // 手机号
EMAIL, // 邮箱
ID_CARD, // 身份证
ADDRESS, // 地址
CUSTOM // 自定义
}
}
2. 在实体类中使用注解
在用户实体类中标记敏感字段:
@Entity
@Table(name = "users")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username; // 用户名
@SensitiveField(type = SensitiveType.PASSWORD)
@Column(nullable = false)
private String password; // 密码 - 敏感字段
@SensitiveField(type = SensitiveType.EMAIL)
@Column(nullable = false, unique = true)
private String email; // 邮箱 - 敏感字段
@SensitiveField(type = SensitiveType.PHONE)
@Column(name = "phone_number", unique = true)
private String phoneNumber; // 手机号 - 敏感字段
@SensitiveField(type = SensitiveType.ID_CARD)
@Column(name = "id_card", unique = true)
private String idCard; // 身份证号 - 敏感字段
@Column(name = "full_name")
private String fullName; // 真实姓名
@SensitiveField(type = SensitiveType.ADDRESS)
@Column(name = "address")
private String address; // 地址 - 敏感字段
}
3. 创建脱敏处理器
实现具体的脱敏逻辑:
@Component
@Slf4j
public class LogSanitizationHandler {
@Autowired
private ObjectMapper objectMapper;
// 手机号正则表达式
private static final Pattern PHONE_PATTERN = Pattern.compile("(\\d{3})\\d{4}(\\d{4})");
// 邮箱正则表达式
private static final Pattern EMAIL_PATTERN = Pattern.compile("(\\w{1})\\w*(\\w{1}@\\w+\\.\\w+)");
// 身份证正则表达式
private static final Pattern ID_CARD_PATTERN = Pattern.compile("(\\d{6})\\d{8}(\\w{4})");
/**
* 对对象进行脱敏处理
*/
public <T> T sanitize(T object) {
if (object == null) {
return null;
}
// 获取对象的所有字段
Field[] fields = object.getClass().getDeclaredFields();
for (Field field : fields) {
try {
field.setAccessible(true);
Object fieldValue = field.get(object);
// 检查字段是否有敏感字段注解
if (field.isAnnotationPresent(SensitiveField.class)) {
SensitiveField annotation = field.getAnnotation(SensitiveField.class);
// 对字段值进行脱敏处理
Object sanitizedValue = sanitizeFieldValue(fieldValue, annotation);
field.set(object, sanitizedValue);
}
} catch (IllegalAccessException e) {
log.warn("无法访问字段: {}", field.getName(), e);
}
}
return object;
}
/**
* 对字段值进行脱敏处理
*/
private Object sanitizeFieldValue(Object value, SensitiveField annotation) {
if (value == null) {
return null;
}
String stringValue = value.toString();
SensitiveField.SensitiveType type = annotation.type();
switch (type) {
case PASSWORD:
return maskPassword(stringValue);
case PHONE:
return maskPhone(stringValue);
case EMAIL:
return maskEmail(stringValue);
case ID_CARD:
return maskIdCard(stringValue);
case ADDRESS:
return maskAddress(stringValue);
case CUSTOM:
default:
return annotation.maskPattern();
}
}
/**
* 脱敏手机号
*/
private String maskPhone(String phone) {
if (phone == null || phone.length() != 11) {
return phone;
}
return PHONE_PATTERN.matcher(phone).replaceAll("$1****$2");
}
/**
* 脱敏邮箱
*/
private String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
return EMAIL_PATTERN.matcher(email).replaceAll("$1****$2");
}
/**
* 将对象转换为脱敏后的JSON字符串
*/
public String toJsonWithSanitization(Object object) {
try {
Object sanitizedObject = sanitize(object);
return objectMapper.writeValueAsString(sanitizedObject);
} catch (JsonProcessingException e) {
log.error("JSON序列化失败", e);
return object.toString(); // 返回原字符串作为备选
}
}
}
4. 创建AOP切面实现自动脱敏
使用AOP切面实现无侵入式的自动脱敏:
@Aspect
@Component
@Order(1) // 设置优先级,确保在其他切面之前执行
@Slf4j
public class LogSanitizationAspect {
@Autowired
private LogSanitizationHandler sanitizationHandler;
/**
* 对方法参数进行脱敏处理
*/
@Around("execution(* com.example.logsanitization.service..*(..)) && " +
"!execution(* com.example.logsanitization.service..*Log(..))")
public Object sanitizeMethodArgs(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取方法参数
Object[] args = joinPoint.getArgs();
// 对参数进行脱敏处理
for (int i = 0; i < args.length; i++) {
if (args[i] != null && !isPrimitiveType(args[i])) {
args[i] = sanitizationHandler.sanitize(args[i]);
}
}
// 执行原方法
Object result = joinPoint.proceed(args);
// 对返回值进行脱敏处理
if (result != null && !isPrimitiveType(result)) {
result = sanitizationHandler.sanitize(result);
}
return result;
}
/**
* 检查对象是否为基础类型
*/
private boolean isPrimitiveType(Object obj) {
return obj instanceof String ||
obj instanceof Number ||
obj instanceof Boolean ||
obj instanceof Character ||
obj.getClass().isPrimitive();
}
}
5. 环境感知配置
根据不同的环境决定是否进行脱敏:
@Component
public class EnvironmentUtil {
private final Environment environment;
@Value("${sanitization.enabled:true}")
private boolean sanitizationEnabled;
public EnvironmentUtil(Environment environment) {
this.environment = environment;
}
/**
* 检查当前是否为生产环境
*/
public boolean isProductionEnvironment() {
String[] activeProfiles = environment.getActiveProfiles();
for (String profile : activeProfiles) {
if ("prod".equalsIgnoreCase(profile)) {
return true;
}
}
return false;
}
/**
* 检查是否需要进行日志脱敏
*/
public boolean shouldSanitize() {
if (isProductionEnvironment()) {
return true; // 生产环境始终脱敏
}
return sanitizationEnabled; // 开发环境根据配置决定
}
}
然后在脱敏处理器中使用环境判断:
public <T> T sanitize(T object) {
if (object == null) {
return null;
}
// 检查是否需要脱敏
if (!environmentUtil.shouldSanitize()) {
log.debug("当前环境不需要脱敏,跳过脱敏处理");
return object;
}
// ... 其他脱敏逻辑
}
6. 配置文件设置
在application.yml中配置脱敏选项:
# 脱敏配置
log:
sanitization:
enabled: true
mode: strict
show-full-info-in-dev: false
force-sanitize-in-prod: true
masking-rules:
password-mask: "******"
phone-mask: "$1****$2"
email-mask: "$1****$2"
id-card-mask: "$1********$2"
address-mask: "$1***$2"
---
spring:
config:
activate:
on-profile: dev
logging:
level:
com.example.logsanitization: DEBUG
org.springframework.web: DEBUG
---
spring:
config:
activate:
on-profile: prod
logging:
level:
com.example.logsanitization: WARN
org.springframework.web: WARN
实际应用效果
通过这套方案,我们可以实现:
开发环境日志:
2023-10-20 10:30:15 [http-nio-8080-exec-2] INFO c.e.service.UserService - 用户登录成功,用户信息:{id=1, username=user123, password=password123, email=user@example.com, phone=13812345678}
生产环境日志:
2023-10-20 10:30:15 [http-nio-8080-exec-2] INFO c.e.service.UserService - 用户登录成功,用户信息:{id=1, username=user123, password=******, email=u****e@exampl, phone=138****5678}
最佳实践建议
- 明确数据分类:清楚区分哪些是敏感数据,哪些不是
- 合理使用注解:只对标记真正敏感的字段使用注解
- 性能考虑:避免在高频调用的方法中进行复杂脱敏
- 测试验证:确保脱敏逻辑在各种边界情况下都能正常工作
- 合规检查:确保符合相关的数据保护法规
总结
通过SpringBoot + 注解 + AOP的组合,我们可以轻松构建一套智能的日志脱敏系统。这套方案具有以下优点:
- 无侵入性:通过注解和AOP实现,不影响原有业务逻辑
- 灵活配置:支持不同环境的不同脱敏策略
- 易于扩展:可以轻松添加新的脱敏规则和类型
- 性能友好:轻量级实现,对系统性能影响极小
在实际项目中,建议根据具体的业务需求和安全要求,合理配置脱敏规则,确保既能满足开发调试的需要,又能保障生产环境的数据安全。
希望这篇文章能对你有所帮助,如果你觉得有用,欢迎关注"服务端技术精选",我会持续分享更多实用的技术干货。
标题:SpringBoot + 日志脱敏 + 敏感字段自动过滤:开发环境可看,生产日志安全合规
作者:jiangyi
地址:http://jiangyi.space/articles/2026/02/02/1769857418955.html
公众号:服务端技术精选
评论
0 评论