news 2026/4/14 17:11:48

生产环境日志脱敏方案:保护用户隐私数据

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
生产环境日志脱敏方案:保护用户隐私数据

被安全部门约谈了一次。

原因是日志里明文打印了用户手机号、身份证号,还被导出到了测试环境。这要是出了事,GDPR罚款能让公司破产。

花了两周时间做日志脱敏,整理一下方案。

为什么要日志脱敏

日志里经常会有:

  • 手机号、身份证号
  • 银行卡号、密码(别笑,真有人打印)
  • 地址、邮箱
  • Token、API Key

这些数据如果:

  • 被运维人员看到 → 内部泄露风险
  • 日志被导出 → 外部泄露风险
  • 日志被攻击者获取 → 直接完蛋

方案一:代码层脱敏

最可控的方式,在打日志的地方处理。

1.1 自定义toString

publicclassUser{privateStringname;privateStringphone;privateStringidCard;@OverridepublicStringtoString(){return"User{"+"name='"+maskName(name)+'\''+", phone='"+maskPhone(phone)+'\''+", idCard='"+maskIdCard(idCard)+'\''+'}';}privateStringmaskPhone(Stringphone){if(phone==null||phone.length()!=11)returnphone;returnphone.substring(0,3)+"****"+phone.substring(7);}privateStringmaskIdCard(StringidCard){if(idCard==null||idCard.length()<10)returnidCard;returnidCard.substring(0,4)+"**********"+idCard.substring(idCard.length()-4);}privateStringmaskName(Stringname){if(name==null||name.length()<2)returnname;returnname.charAt(0)+"*".repeat(name.length()-1);}}

打印出来:

User{name='张*', phone='138****5678', idCard='3201**********1234'}

1.2 注解方式

更优雅一点,用注解标记敏感字段:

// 自定义注解@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public@interfaceSensitive{SensitiveTypetype();}publicenumSensitiveType{PHONE,ID_CARD,NAME,BANK_CARD,EMAIL}// 使用注解publicclassUser{@Sensitive(type=SensitiveType.NAME)privateStringname;@Sensitive(type=SensitiveType.PHONE)privateStringphone;@Sensitive(type=SensitiveType.ID_CARD)privateStringidCard;}
// 脱敏工具类publicclassSensitiveUtil{publicstaticStringmask(Objectobj){if(obj==null)return"null";Class<?>clazz=obj.getClass();StringBuildersb=newStringBuilder(clazz.getSimpleName()+"{");Field[]fields=clazz.getDeclaredFields();for(inti=0;i<fields.length;i++){Fieldfield=fields[i];field.setAccessible(true);Stringvalue;try{ObjectfieldValue=field.get(obj);if(field.isAnnotationPresent(Sensitive.class)){Sensitiveannotation=field.getAnnotation(Sensitive.class);value=doMask(String.valueOf(fieldValue),annotation.type());}else{value=String.valueOf(fieldValue);}}catch(IllegalAccessExceptione){value="N/A";}sb.append(field.getName()).append("='").append(value).append("'");if(i<fields.length-1)sb.append(", ");}returnsb.append("}").toString();}privatestaticStringdoMask(Stringvalue,SensitiveTypetype){if(value==null||"null".equals(value))returnvalue;switch(type){casePHONE:returnvalue.length()==11?value.substring(0,3)+"****"+value.substring(7):value;caseID_CARD:returnvalue.length()>=10?value.substring(0,4)+"**********"+value.substring(value.length()-4):value;caseNAME:returnvalue.length()>=2?value.charAt(0)+"*".repeat(value.length()-1):value;caseBANK_CARD:returnvalue.length()>=8?value.substring(0,4)+"****"+value.substring(value.length()-4):value;caseEMAIL:intatIndex=value.indexOf("@");returnatIndex>2?value.substring(0,2)+"***"+value.substring(atIndex):value;default:returnvalue;}}}

使用:

log.info("用户信息: {}",SensitiveUtil.mask(user));// 输出:用户信息: User{name='张*', phone='138****5678', idCard='3201**********1234'}

方案二:日志框架层脱敏

在Logback/Log4j2层面统一处理。

2.1 Logback自定义Converter

publicclassSensitivePatternConverterextendsClassicConverter{privatestaticfinalPatternPHONE_PATTERN=Pattern.compile("1[3-9]\\d{9}");privatestaticfinalPatternIDCARD_PATTERN=Pattern.compile("\\d{17}[\\dXx]");privatestaticfinalPatternEMAIL_PATTERN=Pattern.compile("[\\w.]+@[\\w.]+");@OverridepublicStringconvert(ILoggingEventevent){Stringmessage=event.getFormattedMessage();// 手机号脱敏message=PHONE_PATTERN.matcher(message).replaceAll(m->m.group().substring(0,3)+"****"+m.group().substring(7));// 身份证脱敏message=IDCARD_PATTERN.matcher(message).replaceAll(m->m.group().substring(0,4)+"**********"+m.group().substring(14));// 邮箱脱敏message=EMAIL_PATTERN.matcher(message).replaceAll(m->{Stringemail=m.group();intatIndex=email.indexOf("@");returnatIndex>2?email.substring(0,2)+"***"+email.substring(atIndex):email;});returnmessage;}}
<!-- logback.xml --><configuration><conversionRuleconversionWord="sensMsg"converterClass="com.example.SensitivePatternConverter"/><appendername="CONSOLE"class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger - %sensMsg%n</pattern></encoder></appender></configuration>

这样所有日志自动脱敏,无需改业务代码。

2.2 Log4j2 RewritePolicy

@Plugin(name="SensitiveRewritePolicy",category="Core",elementType="rewritePolicy")publicclassSensitiveRewritePolicyimplementsRewritePolicy{@OverridepublicLogEventrewrite(LogEventevent){Messagemessage=event.getMessage();StringformattedMessage=message.getFormattedMessage();// 脱敏处理StringmaskedMessage=maskSensitiveData(formattedMessage);returnnewLog4jLogEvent.Builder(event).setMessage(newSimpleMessage(maskedMessage)).build();}privateStringmaskSensitiveData(Stringmessage){// 同上,正则替换returnmessage;}}

方案三:日志采集层脱敏

在Logstash/Filebeat采集时处理。

3.1 Logstash Filter

filter{# 手机号脱敏mutate{gsub=>["message","1[3-9]\d{9}","1**********"]}# 身份证脱敏mutate{gsub=>["message","\d{17}[\dXx]","****"]}# 银行卡脱敏mutate{gsub=>["message","\d{16,19}","****"]}}

3.2 Filebeat Processor

processors:-script:lang:javascriptsource:>function process(event) { var message = event.Get("message"); // 手机号脱敏 message = message.replace(/1[3-9]\d{9}/g, function(match) { return match.substring(0,3) + "****" + match.substring(7); }); event.Put("message", message); }

方案四:落盘后处理

如果历史日志已经有敏感信息,需要清洗。

#!/bin/bash# 日志脱敏脚本LOG_DIR="/var/log/app"# 手机号脱敏find$LOG_DIR-name"*.log"-execsed-i -E's/1([3-9])[0-9]{9}/1\1*******/g'{}\;# 身份证脱敏find$LOG_DIR-name"*.log"-execsed-i -E's/[0-9]{6}[0-9]{8}[0-9]{3}[0-9Xx]/******/g'{}\;

最佳实践

分层防护

代码层 → 日志框架层 → 采集层 → 存储层 ↓ ↓ ↓ ↓ 不打印敏感 自动脱敏 二次脱敏 加密存储

建议至少做两层。

敏感数据分类

级别数据类型处理方式
L1绝密密码、密钥禁止打印
L2机密身份证、银行卡强脱敏
L3敏感手机号、邮箱弱脱敏
L4普通姓名、地址可脱敏可不脱敏

禁止打印的内容

// 密码绝对不能打印log.info("用户登录: {}, 密码: {}",username,password);// 错误// Token不能打印log.info("Token: {}",token);// 错误// API Key不能打印log.info("调用第三方API, key={}",apiKey);// 错误

正确做法:

// 只打印是否存在log.info("用户登录: {}, 密码: {}",username,password!=null?"[已设置]":"[未设置]");// Token只打印部分log.info("Token: {}...",token.substring(0,8));

验证脱敏效果

写个测试用例:

@TestpublicvoidtestSensitiveMask(){Useruser=newUser();user.setName("张三");user.setPhone("13812345678");user.setIdCard("320123199001011234");Stringmasked=SensitiveUtil.mask(user);// 验证脱敏后不包含原始数据assertFalse(masked.contains("13812345678"));assertFalse(masked.contains("320123199001011234"));// 验证脱敏格式正确assertTrue(masked.contains("138****5678"));assertTrue(masked.contains("3201**********1234"));}

日志安全审计

除了脱敏,还要控制日志访问权限。

我们有几台日志服务器分布在不同机房,之前权限管理很乱。现在用星空组网把所有节点连起来后,统一通过跳板机访问,权限管控清晰多了。

总结

日志脱敏方案选择:

方案优点缺点推荐场景
代码层最可控需要改代码新项目
日志框架层全局生效性能有损耗老项目改造
采集层不改代码本地日志仍有敏感信息补充方案
落盘后处理处理历史数据不实时应急处理

核心原则:

  1. 能不打印的就不打印
  2. 必须打印的要脱敏
  3. 脱敏要分级
  4. 多层防护

日志安全这块有经验的欢迎交流~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/12 11:33:43

边缘计算驱动的实时异常检测算法部署指南

边缘侧实时异常检测&#xff1a;从算法到部署的实战全解析在智能制造车间的一台旋转设备上&#xff0c;振动传感器每秒采集上百个数据点。某天凌晨&#xff0c;轴承开始出现微弱的周期性冲击信号——这种变化人耳无法察觉&#xff0c;云端监控系统也因采样间隔过长而错过。但就…

作者头像 李华
网站建设 2026/4/4 1:42:37

【AI时代新生产力工具】:Open-AutoGLM驱动电脑自动化的7个高阶应用场景

第一章&#xff1a;Open-AutoGLM驱动自动化的核心机制Open-AutoGLM 是一种基于生成式语言模型的自动化引擎&#xff0c;其核心在于将自然语言指令转化为可执行的工作流。该机制依赖于语义解析、任务调度与执行反馈三大模块的协同运作&#xff0c;实现从用户意图到系统操作的端到…

作者头像 李华
网站建设 2026/4/13 5:29:05

LangFlow事件循环机制解析

LangFlow事件循环机制解析 在构建大语言模型&#xff08;LLM&#xff09;应用的今天&#xff0c;开发者常常面临一个尴尬的局面&#xff1a;明明只是想快速验证一个想法&#xff0c;却不得不花大量时间写胶水代码、调试组件连接、反复重启服务查看输出。这种低效的开发流程严重…

作者头像 李华
网站建设 2026/4/13 11:38:52

开源Open-AutoGLM地址到底在哪?10分钟带你找到官方资源并部署上线

第一章&#xff1a;开源的Open-AutoGLM地址在哪Open-AutoGLM 是一个面向自动化自然语言处理任务的开源框架&#xff0c;由深度学习与大模型研究团队联合发布&#xff0c;旨在降低大语言模型在实际场景中的应用门槛。该项目已在主流代码托管平台公开源码&#xff0c;便于开发者查…

作者头像 李华
网站建设 2026/4/11 9:27:25

Open-AutoGLM落地实战(手机端大模型部署全攻略)

第一章&#xff1a;Open-AutoGLM落地实战&#xff08;手机端大模型部署全攻略&#xff09;在移动端部署大语言模型已成为智能应用开发的关键环节。Open-AutoGLM 作为开源的轻量化 GLM 架构推理框架&#xff0c;专为资源受限设备优化&#xff0c;支持在 Android 和 iOS 平台高效…

作者头像 李华