日志脱敏方案
目录
日志脱敏方案
场景:在开发环境中,往往日志中会泄露个人信息,这时候需要对日志中的个人信息做脱密处理
通过继承MessageConverter来实现
/**
* 日志脱敏工具
* @author wuhming
* @date 2023-10-26
*/
@Slf4j
public class SensitiveLogDataConverter extends MessageConverter {
// 脱敏符号 *
public static final String SYMBOL_STAR = "*";
/**
* 0 加密前三位
* 1 加密中间四位
* 2 加密后面四位
*/
public static Integer logMode = 1;
/**
* 是否开启加密规则 默认为false
*/
public static boolean logSensitive = false;
/**
* 手机号
*/
private static final Pattern PHONE_PATTERN = Pattern.compile("(?<!\\d)(1\\d{10})(?!\\d)");
/**
* 验证带区号的
*/
private static final Pattern LANDLINE_AREA = Pattern.compile("[0][1-9]{2,3}-[0-9]{5,10}");
/**
* 验证没有区号的
*/
private static final Pattern LANDLINE = Pattern.compile("[1-9]{1}[0-9]{5,8}");
/**
* 证件号码
*/
private static final Pattern ID_NUMBER = Pattern.compile("((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|81|82|91)[0-9]{4}(18|19|20)[0-9]{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)[0-9]{3}[0-9Xx]");
/**
* 银行卡
*/
//private static final Pattern BANK_CARD_NUMBER = Pattern.compile("^(?:62|88|99|19\\d{2}|4\\d{3}|5[1-5]\\d{2}|6\\d{3}|81\\d{2})\\d{10}|^62\\d{12,17}|^[0-9]{16,19}$");
/**
* 银行卡
*/
private static final Pattern BANK_CARD_NUMBER = Pattern.compile("[0-9]{16,19}");
/**
* 邮箱符号
*/
private static final Pattern EMAIL = Pattern.compile("\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}");
//private static final Pattern ADDRESS = Pattern.compile("/^([\\u4e00-\\u9fa5]+(?:省|市|自治区)){0,1}([\\u4e00-\\u9fa5]+(?:市|区|县|州|盟)){0,1}([\\u4e00-\\u9fa5]+(?:街道|镇|乡)){0,1}([\\u4e00-\\u9fa5]+(?:号|村|社)){0,1}([\\u4e00-\\u9fa5]+(?:路|街|巷)){0,1}([\\u4e00-\\u9fa5]+(?:弄|号楼|栋|单元)){0,1}([0-9A-Za-z]{5,}){0,1}$/");
//
private static final Pattern ADDRESS = Pattern.compile("([^区]+区)|([^镇]+镇)|([^路]+路)|([^街]+街)|([^单元]+单元)|([^公司]+公司)|([^室]+室)|([^户]+户)");
static {
Object value = null;
try {
value = YmlReadUtil.getValue("application.yml", "","logging.enable-sensitive");
} catch (Exception e) {
e.printStackTrace();
}
if(value != null){
if(value instanceof Boolean){
logSensitive = (Boolean) value;
}else if(value instanceof String){
logSensitive = Boolean.parseBoolean((String) value);
}
}
}
@Override
public String convert(ILoggingEvent event) {
String logMsg = event.getFormattedMessage();
if (logSensitive) {
try {
final Set<String> email = validateEmail(logMsg);
if (!email.isEmpty()) {
for (String param : email) {
logMsg = logMsg.replaceAll(param, emailEncrypt(param));
}
}
final Set<String> phoneNumber = validatePhoneNumber(logMsg);
//电话号码加密
if (!phoneNumber.isEmpty()) {
for (String param : phoneNumber) {
logMsg = logMsg.replaceAll(param, mobileEncrypt(param));
}
}
final Set<String> landlineArea = validateLandlineArea(logMsg);
if (!phoneNumber.isEmpty()) {
for (String param : landlineArea) {
logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
}
}
final Set<String> IDNumber = validateIDNumber(logMsg);
if (!IDNumber.isEmpty()) {
for (String param : IDNumber) {
logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
}
}
final Set<String> cardNumber = validateCardNumber(logMsg);
if (!cardNumber.isEmpty()) {
for (String param : cardNumber) {
logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
}
}
final Set<String> bankCardNumber = validateBankCardNumber(logMsg);
if (!bankCardNumber.isEmpty()) {
for (String param : bankCardNumber) {
logMsg = logMsg.replaceAll(param, idNumberEncrypt(param));
}
}
logMsg = dealAddress(logMsg);
return logMsg;
} catch (Exception e) {
e.printStackTrace();
return super.convert(event);
}
} else {
return logMsg;
}
}
private static String dealAddress(String logMsg) {
char[] charArray = logMsg.toCharArray();
if (logMsg.contains("市")) {
while (logMsg.contains("市")) {
for (int i = logMsg.indexOf("市"); i < logMsg.length() - 1; i++) {
if (checkAddressEnd(charArray[i])) {
break;
}
charArray[i] = SYMBOL_STAR.charAt(0);
}
logMsg = String.valueOf(charArray);
}
} else {
if (logMsg.contains("省")) {
while (logMsg.contains("省")) {
for (int i = logMsg.indexOf("省"); i < logMsg.length() - 1; i++) {
if (checkAddressEnd(charArray[i])) {
break;
}
charArray[i] = SYMBOL_STAR.charAt(0);
}
logMsg = String.valueOf(charArray);
}
}
}
return String.valueOf(charArray);
}
private static boolean checkAddressEnd(char c) {
return " ".equals(String.valueOf(c)) || ":".equals(String.valueOf(c)) || ";".equals(String.valueOf(c)) ||
":".equals(String.valueOf(c)) || ";".equals(String.valueOf(c)) || ",".equals(String.valueOf(c))
|| ",".equals(String.valueOf(c)) || "“".equals(String.valueOf(c)) || "\"".equals(String.valueOf(c));
}
/**
* 获取日志字符串内容中符合手机号
*/
private static Set<String> validatePhoneNumber(String param) {
Set<String> set = new HashSet<>();
// 匹配手机号
Matcher phoneMatcher = PHONE_PATTERN.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateLandline(String param) {
Set<String> set = new HashSet<>();
// 匹配手机号
Matcher phoneMatcher = LANDLINE.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateLandlineArea(String param) {
Set<String> set = new HashSet<>();
// 匹配手机号
Matcher phoneMatcher = LANDLINE_AREA.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateIDNumber(String param) {
Set<String> set = new HashSet<>();
Matcher phoneMatcher = ID_NUMBER.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateCardNumber(String param) {
Set<String> set = new HashSet<>();
Matcher phoneMatcher = BANK_CARD_NUMBER.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateBankCardNumber(String param) {
Set<String> set = new HashSet<>();
Matcher phoneMatcher = BANK_CARD_NUMBER.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateEmail(String param) {
Set<String> set = new HashSet<>();
Matcher phoneMatcher = EMAIL.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
private static Set<String> validateAddress(String param) {
Set<String> set = new HashSet<>();
Matcher phoneMatcher = ADDRESS.matcher(param);
while (phoneMatcher.find()) {
set.add(phoneMatcher.group());
}
return set;
}
/**
* 手机号脱敏
*
* @param mobile
* @return
*/
public static String mobileEncrypt(String mobile) {
if (StrUtil.isEmpty(mobile) || (mobile.length() != 11)) {
return mobile;
}
switch (logMode) {
case 0:
return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "***$2$3");
case 1:
return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1****$3");
case 2:
return mobile.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1$2****");
default:
return mobile;
}
}
public static String idNumberEncrypt(String idNumber) {
if (StrUtil.isEmpty(idNumber)) {
return idNumber;
}
return filterString(idNumber, SYMBOL_STAR);
}
public static String emailEncrypt(String email) {
if (StrUtil.isEmpty(email)) {
return email;
}
return filterEmail(email, SYMBOL_STAR);
}
public static String addressEncrypt(String address) {
if (StrUtil.isEmpty(address)) {
return address;
}
return filterAddress(address, SYMBOL_STAR);
}
public static String filterAddress(String address, String symbol) {
if (StrUtil.isNotBlank(address) && !address.contains("省") && !address.contains("市")) {
return buildSymbol(symbol, address.length());
}
return address;
}
public static String filterEmail(String email, String symbol) {
if (email.contains("@")) {
int i = email.lastIndexOf("@");
String front = email.substring(0, i);
String rear = email.substring(i).replace("@", "#");
return filterString(front, symbol).concat(rear);
}
return email;
}
public static String filterString(String s, String symbol) {
if (StrUtil.isNotBlank(s)) {
char[] charArray = s.toCharArray();
switch (logMode) {
//隐藏前三位
case 0:
for (int i = 0; i < Math.min(3, charArray.length); i++) {
charArray[i] = symbol.charAt(0);
}
return String.valueOf(charArray);
//隐藏中间4位
case 1:
for (int i = Math.max((charArray.length / 2 - 2), 0); i < (Math.min((charArray.length / 2 + 2), charArray.length)); i++) {
charArray[i] = symbol.charAt(0);
}
return String.valueOf(charArray);
case 2:
for (int i = Math.max((charArray.length - 1), 0); i >= (Math.max((charArray.length - 4), 0)); i--) {
charArray[i] = symbol.charAt(0);
}
return String.valueOf(charArray);
default:
break;
}
}
return s;
}
/**
* @param symbol 符号
* @param num 数量
* @return 创建一定数量的相同符号字符串
*/
public static String buildSymbol(String symbol, int num) {
if (num < 0) {
throw new IndexOutOfBoundsException("-1");
}
StringBuilder value = new StringBuilder();
while (--num >= 0) {
value.append(symbol);
}
return value.toString();
}
}
修改logbak-spring.xml文件
<!--配置脱敏转换类-->
<conversionRule conversionWord="m" converterClass="com.example.spring.boot.test.logbak.SensitiveLogDataConverter"/>
通过配置文件:application.yml来启用脱敏
logging:
enable-sensitive: true
测试
@Test
void test10(){
Map<String, String> hashMap = new HashMap<>();
hashMap.put("phone", "15692381567");
hashMap.put("identityCard", "110001200300001234");
hashMap.put("address", "四川省成都市天府新区");
hashMap.put("email", "1571682356@qq.com");
log.info("{}", JSON.toJSONString(hashMap));
}
结果
2023-11-01 17:16:19.892 INFO 75957 --- [ main] c.e.s.b.t.SpringbootTestApplicationTests test10 181 : {"address":"四川省成都****","phone":"156****1567","identityCard":"1100012****0001234","email":"157****356#qq.com"}