【架构实战】数据脱敏与隐私保护:合规是底线
一、日志里打印了用户手机号被安全部门约谈2021年安全部门扫描发现我们的日志里明文打印了用户手机号和身份证号。根据《个人信息保护法》这属于违规行为。我们被要求限期整改否则面临罚款。从那以后数据脱敏成了我们的必修课。二、脱敏规则/** * 脱敏工具类 */publicclassMaskUtils{/** 手机号脱敏138****1234 */publicstaticStringmaskPhone(Stringphone){if(phonenull||phone.length()7)returnphone;returnphone.substring(0,3)****phone.substring(phone.length()-4);}/** 身份证脱敏3301****1234 */publicstaticStringmaskIdCard(StringidCard){if(idCardnull||idCard.length()8)returnidCard;returnidCard.substring(0,4)****idCard.substring(idCard.length()-4);}/** 邮箱脱敏t***example.com */publicstaticStringmaskEmail(Stringemail){if(emailnull||!email.contains())returnemail;String[]partsemail.split();returnparts[0].charAt(0)***parts[1];}/** 姓名脱敏张** */publicstaticStringmaskName(Stringname){if(namenull||name.length()2)returnname;returnname.charAt(0)**;}/** 银行卡脱敏**** **** **** 1234 */publicstaticStringmaskBankCard(StringcardNo){if(cardNonull||cardNo.length()4)returncardNo;return**** **** **** cardNo.substring(cardNo.length()-4);}/** 地址脱敏浙江省杭州市**** */publicstaticStringmaskAddress(Stringaddress){if(addressnull||address.length()6)returnaddress;returnaddress.substring(0,6)****;}}2.1 MyBatis脱敏拦截器/** * 查询结果自动脱敏 */Intercepts({Signature(typeExecutor.class,methodquery,args{MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class})})ComponentpublicclassDataMaskInterceptorimplementsInterceptor{OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{Objectresultinvocation.proceed();if(resultinstanceofList){for(Objectitem:(List?)result){maskSensitiveFields(item);}}elseif(result!null){maskSensitiveFields(result);}returnresult;}privatevoidmaskSensitiveFields(Objectobj){if(objnull)return;for(Fieldfield:obj.getClass().getDeclaredFields()){Sensitivesensitivefield.getAnnotation(Sensitive.class);if(sensitive!nullfield.getType()String.class){field.setAccessible(true);try{Stringvalue(String)field.get(obj);if(value!null){field.set(obj,maskByType(value,sensitive.type()));}}catch(IllegalAccessExceptione){// ignore}}}}privateStringmaskByType(Stringvalue,SensitiveTypetype){switch(type){casePHONE:returnMaskUtils.maskPhone(value);caseID_CARD:returnMaskUtils.maskIdCard(value);caseEMAIL:returnMaskUtils.maskEmail(value);caseNAME:returnMaskUtils.maskName(value);caseBANK_CARD:returnMaskUtils.maskBankCard(value);caseADDRESS:returnMaskUtils.maskAddress(value);default:returnvalue;}}}/** * 敏感字段注解 */Target(ElementType.FIELD)Retention(RetentionPolicy.RUNTIME)publicinterfaceSensitive{SensitiveTypetype();}// 使用DatapublicclassUserVO{privateLongid;privateStringusername;Sensitive(typeSensitiveType.PHONE)privateStringphone;Sensitive(typeSensitiveType.ID_CARD)privateStringidCard;Sensitive(typeSensitiveType.EMAIL)privateStringemail;}三、日志脱敏/** * 日志脱敏拦截器 */ComponentpublicclassLogMaskFilterimplementsFilter{OverridepublicvoiddoFilter(ServletRequestrequest,ServletResponseresponse,FilterChainchain)throwsIOException,ServletException{ContentCachingRequestWrapperwrappedRequestnewContentCachingRequestWrapper((HttpServletRequest)request);chain.doFilter(wrappedRequest,response);// 日志中记录请求体脱敏后StringbodynewString(wrappedRequest.getContentAsByteArray());StringmaskedmaskSensitiveData(body);log.info(Request: uri{}, body{},((HttpServletRequest)request).getRequestURI(),masked);}privateStringmaskSensitiveData(Stringdata){// 正则匹配手机号datadata.replaceAll((1[3-9]\\d)\\d{4}(\\d{4}),$1****$2);// 正则匹配身份证号datadata.replaceAll((\\d{4})\\d{10}(\\d{4}),$1**********$2);// 正则匹配邮箱datadata.replaceAll((\\w)\\w(\\w\\.\\w),$1***$2);returndata;}}四、加密存储/** * 数据库字段加密 */ComponentpublicclassFieldEncryptUtil{AutowiredprivateAesEncryptoraesEncryptor;/** * 加密 */publicStringencrypt(Stringplaintext){returnaesEncryptor.encrypt(plaintext);}/** * 解密 */publicStringdecrypt(Stringciphertext){returnaesEncryptor.decrypt(ciphertext);}}/** * MyBatis加密拦截器 */Intercepts({Signature(typeExecutor.class,methodupdate),Signature(typeExecutor.class,methodquery)})ComponentpublicclassFieldEncryptInterceptorimplementsInterceptor{AutowiredprivateFieldEncryptUtilencryptUtil;OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{// 写入时加密if(update.equals(invocation.getMethod().getName())){encryptParams(invocation.getArgs());}Objectresultinvocation.proceed();// 查询时解密if(query.equals(invocation.getMethod().getName())){decryptResult(result);}returnresult;}}五、踩坑实录坑1脱敏了但数据库里没加密API返回脱敏了但数据库里是明文存储被SQL注入后泄露。解决敏感字段数据库加密存储。坑2脱敏规则不统一不同服务的脱敏方式不同A服务手机号3位4位B服务4位4位。解决统一脱敏工具类强制使用。坑3日志脱敏遗漏有些日志是第三方库打印的没有经过我们的脱敏过滤器。解决Logback脱敏插件统一处理所有日志输出。坑4加密后无法查询手机号加密存储后无法按手机号查询。解决存储脱敏值加密值用脱敏值查询或使用可搜索加密。坑5密钥管理加密密钥硬编码在代码里不安全。解决密钥存储在KMS密钥管理服务运行时动态获取。六、总结数据隐私保护要点层面方案传输HTTPS 字段加密存储数据库字段加密展示脱敏返回日志自动脱敏密钥KMS管理血的教训数据安全不是可选项。一次数据泄露的代价远超安全建设的成本。思考题你的系统做了哪些数据脱敏措施个人观点仅供参考