本文目录导读:

在Java中实现日志脱敏,通常有以下几种常见方案,我会从简单到复杂,给出具体的代码案例。
基于正则表达式的脱敏工具类
这是最基础、最常用的方式,适用于Logback、Log4j等日志框架。
基础脱敏工具类
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DesensitizationUtil {
/**
* 手机号脱敏:保留前3后4,中间用****代替
*/
public static String maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
return phone;
}
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
/**
* 身份证号脱敏:保留前6后4
*/
public static String maskIdCard(String idCard) {
if (idCard == null || idCard.length() < 10) {
return idCard;
}
return idCard.replaceAll("(\\d{6})\\d{8}(\\d{4})", "$1********$2");
}
/**
* 邮箱脱敏:保留用户名首字母和域名
*/
public static String maskEmail(String email) {
if (email == null || !email.contains("@")) {
return email;
}
return email.replaceAll("(\\w?)(\\w+)(@\\w+)", "$1***$3");
}
/**
* 银行卡号脱敏:保留前4后4
*/
public static String maskBankCard(String cardNo) {
if (cardNo == null || cardNo.length() < 8) {
return cardNo;
}
return cardNo.replaceAll("(\\d{4})\\d{8}(\\d{4})", "$1********$2");
}
/**
* 姓名脱敏:保留姓氏,名字用*代替
*/
public static String maskName(String name) {
if (name == null || name.length() < 2) {
return name;
}
if (name.length() == 2) {
return name.replaceAll("(.)(.)", "$1*");
}
StringBuilder sb = new StringBuilder(name);
for (int i = 1; i < sb.length(); i++) {
sb.setCharAt(i, '*');
}
return sb.toString();
}
/**
* 通用脱敏方法:将JSON字符串中的敏感字段替换
*/
public static String maskSensitiveJson(String json) {
if (json == null || json.isEmpty()) {
return json;
}
// 替换手机号
json = json.replaceAll("\"phone\"\\s*:\\s*\"(\\d{3})\\d{4}(\\d{4})\"",
"\"phone\":\"$1****$2\"");
// 替换身份证
json = json.replaceAll("\"idCard\"\\s*:\\s*\"(\\d{6})\\d{8}(\\d{4})\"",
"\"idCard\":\"$1********$2\"");
// 替换银行卡
json = json.replaceAll("\"bankCard\"\\s*:\\s*\"(\\d{4})\\d{8}(\\d{4})\"",
"\"bankCard\":\"$1********$2\"");
return json;
}
}
使用示例
public class DemoService {
private static final Logger log = LoggerFactory.getLogger(DemoService.class);
public void processUser(User user) {
// 手动脱敏后打印
log.info("处理用户信息 - 姓名: {}, 手机: {}, 邮箱: {}",
DesensitizationUtil.maskName(user.getName()),
DesensitizationUtil.maskPhone(user.getPhone()),
DesensitizationUtil.maskEmail(user.getEmail()));
// 或者打印JSON时脱敏
String userJson = JSON.toJSONString(user);
log.info("用户信息JSON: {}", DesensitizationUtil.maskSensitiveJson(userJson));
}
}
基于注解的脱敏方案
使用注解标记敏感字段,在序列化时自动脱敏。
自定义注解
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SensitiveField {
/**
* 脱敏类型
*/
SensitiveType type() default SensitiveType.CUSTOM;
/**
* 保留前几位
*/
int keepPrefix() default 0;
/**
* 保留后几位
*/
int keepSuffix() default 0;
/**
* 替换字符
*/
char replaceChar() default '*';
enum SensitiveType {
PHONE, // 手机号
ID_CARD, // 身份证
EMAIL, // 邮箱
BANK_CARD, // 银行卡
NAME, // 姓名
CUSTOM // 自定义
}
}
脱敏序列化器
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import java.io.IOException;
import java.util.Objects;
public class SensitiveSerializer extends JsonSerializer<String>
implements ContextualSerializer {
private SensitiveField sensitiveField;
public SensitiveSerializer() {}
public SensitiveSerializer(SensitiveField sensitiveField) {
this.sensitiveField = sensitiveField;
}
@Override
public void serialize(String value, JsonGenerator gen,
SerializerProvider serializers) throws IOException {
gen.writeString(mask(value));
}
@Override
public JsonSerializer<?> createContextual(SerializerProvider prov,
BeanProperty property) {
if (property != null) {
SensitiveField annotation = property.getAnnotation(SensitiveField.class);
if (annotation != null) {
return new SensitiveSerializer(annotation);
}
}
return prov.findValueSerializer(property.getType(), property);
}
private String mask(String value) {
if (value == null || value.isEmpty()) {
return value;
}
SensitiveField.SensitiveType type = sensitiveField.type();
return switch (type) {
case PHONE -> maskPhone(value);
case ID_CARD -> maskIdCard(value);
case EMAIL -> maskEmail(value);
case BANK_CARD -> maskBankCard(value);
case NAME -> maskName(value);
case CUSTOM -> maskCustom(value);
};
}
private String maskPhone(String phone) {
if (phone.length() < 7) return phone;
return phone.substring(0, 3) + "****" + phone.substring(7);
}
private String maskIdCard(String idCard) {
if (idCard.length() < 10) return idCard;
return idCard.substring(0, 6) + "********" + idCard.substring(14);
}
private String maskEmail(String email) {
if (!email.contains("@")) return email;
String[] parts = email.split("@");
if (parts[0].length() <= 1) return email;
return parts[0].charAt(0) + "***@" + parts[1];
}
private String maskBankCard(String cardNo) {
if (cardNo.length() < 8) return cardNo;
return cardNo.substring(0, 4) + "********" + cardNo.substring(12);
}
private String maskName(String name) {
if (name.length() <= 1) return name;
char[] chars = name.toCharArray();
for (int i = 1; i < chars.length; i++) {
chars[i] = '*';
}
return new String(chars);
}
private String maskCustom(String value) {
int prefix = sensitiveField.keepPrefix();
int suffix = sensitiveField.keepSuffix();
char replaceChar = sensitiveField.replaceChar();
if (prefix + suffix >= value.length()) {
return value;
}
StringBuilder sb = new StringBuilder(value);
for (int i = prefix; i < value.length() - suffix; i++) {
sb.setCharAt(i, replaceChar);
}
return sb.toString();
}
}
实体类使用
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
public class User {
@SensitiveField(type = SensitiveField.SensitiveType.NAME)
private String name;
@SensitiveField(type = SensitiveField.SensitiveType.PHONE)
private String phone;
@SensitiveField(type = SensitiveField.SensitiveType.EMAIL)
private String email;
@SensitiveField(type = SensitiveField.SensitiveType.ID_CARD)
private String idCard;
@SensitiveField(type = SensitiveField.SensitiveType.CUSTOM,
keepPrefix = 4, keepSuffix = 4, replaceChar = '#')
private String accountNo;
// getters and setters...
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", idCard='" + idCard + '\'' +
", accountNo='" + accountNo + '\'' +
'}';
}
}
Logback的ConversionRule实现
通过自定义Logback的转换规则,实现全局自动脱敏。
自定义转换器
import ch.qos.logback.classic.pattern.MessageConverter;
import ch.qos.logback.classic.spi.ILoggingEvent;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SensitiveConverter extends MessageConverter {
// 手机号正则
private static final Pattern PHONE_PATTERN =
Pattern.compile("1[3-9]\\d{9}");
// 身份证正则
private static final Pattern ID_CARD_PATTERN =
Pattern.compile("\\d{17}[\\dxX]");
// 邮箱正则
private static final Pattern EMAIL_PATTERN =
Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");
@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
return maskSensitive(message);
}
private String maskSensitive(String message) {
if (message == null) {
return null;
}
// 手机号脱敏
Matcher phoneMatcher = PHONE_PATTERN.matcher(message);
while (phoneMatcher.find()) {
String phone = phoneMatcher.group();
message = message.replace(phone,
phone.substring(0, 3) + "****" + phone.substring(7));
}
// 身份证脱敏
Matcher idCardMatcher = ID_CARD_PATTERN.matcher(message);
while (idCardMatcher.find()) {
String idCard = idCardMatcher.group();
message = message.replace(idCard,
idCard.substring(0, 6) + "********" + idCard.substring(14));
}
// 邮箱脱敏
Matcher emailMatcher = EMAIL_PATTERN.matcher(message);
while (emailMatcher.find()) {
String email = emailMatcher.group();
String[] parts = email.split("@");
if (parts[0].length() > 1) {
message = message.replace(email,
parts[0].charAt(0) + "***@" + parts[1]);
}
}
return message;
}
}
logback.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 注册自定义转换器 -->
<conversionRule conversionWord="msg"
converterClass="com.example.SensitiveConverter" />
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE" />
</root>
</configuration>
AOP切面实现
使用Spring AOP对特定方法进行日志脱敏。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogDesensitizationAspect {
private static final Logger log = LoggerFactory.getLogger(LogDesensitizationAspect.class);
@Around("@annotation(com.example.annotation.LogDesensitization)")
public Object aroundLogMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// 处理参数
Object[] args = joinPoint.getArgs();
Object[] maskedArgs = new Object[args.length];
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof String) {
maskedArgs[i] = maskString((String) args[i]);
} else {
maskedArgs[i] = maskObject(args[i]);
}
}
// 打印脱敏后的参数
log.info("调用方法: {}, 参数: {}",
joinPoint.getSignature().getName(),
Arrays.toString(maskedArgs));
// 执行原方法
Object result = joinPoint.proceed();
// 处理返回值
if (result != null) {
Object maskedResult = maskObject(result);
log.info("方法返回: {}", maskedResult);
}
return result;
}
private String maskString(String value) {
// 对字符串进行脱敏处理
if (value.matches("1[3-9]\\d{9}")) {
return value.substring(0, 3) + "****" + value.substring(7);
}
// 其他字符串脱敏规则...
return value;
}
private Object maskObject(Object obj) {
// 使用Jackson或Gson序列化后脱敏
if (obj != null) {
try {
String json = new ObjectMapper().writeValueAsString(obj);
return DesensitizationUtil.maskSensitiveJson(json);
} catch (Exception e) {
return obj;
}
}
return obj;
}
}
// 自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogDesensitization {
}
// 使用示例
@Service
public class UserService {
@LogDesensitization
public User getUserById(String userId) {
// 方法实现...
return user;
}
}
最佳实践建议
配置驱动的脱敏规则
@Component
@ConfigurationProperties(prefix = "sensitive")
public class SensitiveConfig {
private List<String> fields = new ArrayList<>();
private Map<String, Integer> keepPrefix = new HashMap<>();
private Map<String, Integer> keepSuffix = new HashMap<>();
// getters and setters...
}
// application.yml
// sensitive:
// fields:
// - phone
// - idCard
// - email
// keepPrefix:
// phone: 3
// idCard: 6
// email: 1
// keepSuffix:
// phone: 4
// idCard: 4
// email: 0
性能优化建议
- 缓存正则表达式:避免重复编译Pattern
- 批量处理:对于大量日志,使用缓冲区批处理
- 异步处理:对日志脱敏使用异步线程处理
- 条件判断:只在必要的时候进行脱敏
安全建议
- 生产环境:必须开启日志脱敏
- 开发环境:可以关闭脱敏便于调试
- 审计日志:记录脱敏前后的转换操作
- 定期审查:检查是否有新的敏感字段需要脱敏
根据实际需求,你可以选择最适合的方案,建议在生产环境中至少使用前两种方案的组合,既能保证灵活性,又能保证性能。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播