企业级数据脱敏方案!
最近几年经常发生用户数据泄漏的企业事件,给企业带来危机。数据脱随着用户对个人隐私数据的敏方重视和法律法规的完善,数据安全显得愈发重要。企业一方面可以加强权限管理,数据脱减少能够接触数据的敏方人员以及导出数据加强审批。另一方面,企业还需要从技术上对用户隐私数据进行脱敏处理,数据脱提高数据的敏方安全性。
数据脱敏方法有很多种,企业大致可以按照以下进行分类:
隐藏法: 只显示敏感信息的数据脱部分内容,其他部分进行遮挡,敏方比较常见使用星号替代。企业这种方式日常比较多见,数据脱比如手机号,敏方银行卡号等只显示后面和后面几位,好处是虽然只是部分内容显示,但足够提供有效信息,同时不会暴露完整数据。混淆法: 对原有数据截断、替换、隐藏、数字进行随机移位,使得原有数据完全失真或者部分失真,混淆真假。加密: 通过加密密钥和算法对敏感数据进行加密得到密文,亿华云计算密文可见但是完全没有可读意义,是脱敏最彻底的方法。其中对称加密还能密钥解密可以从密文恢复原始数据。比如密码保存采用非对称加密,手机号存储时采用对称加密。用户的敏感数据包含姓名、电话号码、身份证、银行卡号、电子邮件、家庭住址、登录密码等等。需要考虑数据的敏感程度、数据安全要求以及实际业务使用场景选择合适的脱敏方法。Hutool包里面提供了许多常用的脱敏方法。
企业脱敏方案
企业如何实现脱敏?我们先来看典型的系统数据交互链路,数据需要经过数据库、后端应用、app端。
图片
数据库脱敏方案
数据库脱敏方法根据业务具体要求选择合适脱敏方法。脱敏地点可以在应用中手动脱敏,当然这种方法不常用,改动点多对业务侵入大。
另外一种方案在ORM框架中修改sql实现,其中mybatis框架为java后端系统中最常用的框架。mybatis自带拦截器扩展,允许在映射语句执行过程中的某一点进行拦截调用。关注公众号:码猿技术专栏,回复关键词:1111 获取阿里内部Java性能调优手册!默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
Executor: 拦截执行器的方法,例如 update、query、commit、rollback 等。可以用来实现缓存、事务、分页等功能。ParameterHandler: 拦截参数处理器的方法,例如 setParameters 等。可以用来转换或加密参数等功能。ResultSetHandler: 拦截结果集处理器的方法,例如 handleResultSets、handleOutputParameters 等。可以用来转换或过滤结果集等功能。源码下载StatementHandler: 拦截语句处理器的方法,例如 prepare、parameterize、batch、update、query 等。可以用来修改 SQL 语句、添加参数、记录日志等功能。
Mybatis执行流程
数据库脱敏另外一个问题是历史数据问题。历史原因最开始的技术方案保存明文,所以脱敏时需要做到平滑脱敏。要做到平滑脱敏,可按照如下流程:
新增脱敏字段: 在源表上新增脱敏字段。数据双写: 源字段和脱敏字段都写入数据。历史数据迁移: 历史数据迁移,刷入脱敏字段。读切换脱敏字段: 从脱敏字段读取数据返回。清空源字段: 确保所有流程都正确的情况下,清空源字段。本文mybatis实现数据库加解密为例。
1.表里面新增脱敏字段,示例中脱敏新字段格式规范为源字段添加encrypt后缀。vo里面添加脱敏注解标记。
复制public class Employee { private Long id; private String name; @EncryptTag private String mobile; private String mobileEncrypt; private String email; private double salary; }1.2.3.4.5.6.7.8.9.10.11.2.实现自定义拦截。
复制/*** ** 加密拦截 ***/ @Intercepts({@Signature( type = Executor.class, method = "update", args = {MappedStatement.class, Object.class} ), @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class} ), @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} )}) public class EncryptPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { MappedStatement mappedStatement = (MappedStatement)invocation.getArgs()[0]; Object param = invocation.getArgs()[1]; PluginService.encrypt(invocation, param); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } } /*** ** 解密拦截 ***/ @Intercepts({@Signature( type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class} )}) public class DecryptPlugin implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object result = invocation.proceed(); if (result != null && result instanceof List) { this.decrypt(((List) result).iterator()); } return result; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { } private void decrypt(Iterator iterator) throws Throwable { while(iterator.hasNext()) { Object object = iterator.next(); PluginService.decrypt(object); } } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.3.实现sql修改,完成加解密逻辑。
复制public class PluginService { privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(PluginService.class); privatestaticfinal Map<String, List<Field>> ENCRYPT_TAG_FIELDS = new ConcurrentHashMap(); public static void encrypt(Invocation invocation, Object object) throws Throwable { if (object.getClass().isArray()) { int length = Array.getLength(object); if (length <= 0) { return; } for (int i = 0; i < length; ++i) { encryptSingleObject(Array.get(object, i)); } } elseif (object instanceof Collection) { Collection collection = (Collection) object; Iterator itr = collection.iterator(); while (itr.hasNext()) { Object item = itr.next(); encryptSingleObject(item); } } else { encryptSingleObject(object); } } private static void encryptSingleObject(Object object) throws Throwable { if (object != null) { String className = object.getClass().getName(); List<Field> EncryptTagFields = ENCRYPT_TAG_FIELDS.get(className); if (EncryptTagFields == null) { EncryptTagFields = findEncryptTagFields(object); ENCRYPT_TAG_FIELDS.putIfAbsent(className, EncryptTagFields); } encryptFields(object, EncryptTagFields); } } private static void encryptFields(Object object, List<Field> EncryptTagFields) throws Throwable { if (object != null && !EncryptTagFields.isEmpty()) { String[] originalValues = new String[EncryptTagFields.size()]; for(int i = 0; i < EncryptTagFields.size(); ++i) { Field field = (Field)EncryptTagFields.get(i); String value = (String)field.get(object); originalValues[i] = value; } for(int i = 0; i < EncryptTagFields.size(); ++i) { Field field = (Field)EncryptTagFields.get(i); String value = originalValues[i]; if (value == null) { continue; } Field encryptField = getEncryptField(object, field); if (encryptField == null) { continue; } String encryptValue = encryptFieldValue(value); encryptField.set(object, encryptValue); field.set(object, null); } } } private static String encryptFieldValue(String value) { String encryptValue = value + "encrypt"; return encryptValue; } public static void decrypt(Object object) throws Throwable { if (object == null) { return; } String className = object.getClass().getName(); List<Field> encryptTagFields = ENCRYPT_TAG_FIELDS.get(className); if (encryptTagFields == null) { encryptTagFields = findEncryptTagFields(object); ENCRYPT_TAG_FIELDS.putIfAbsent(className, encryptTagFields); } decryptFields(object, encryptTagFields); } private static void decryptFields(Object object, List<Field> encryptTagFields) throws Throwable { if (encryptTagFields.isEmpty()) { return; } for (int i = 0; i < encryptTagFields.size(); ++i) { Field field = encryptTagFields.get(i); Field encryptField = getEncryptField(object, field); Object fieldValue = encryptField.get(object); if (fieldValue == null) { continue; } if (fieldValue instanceof String) { String value = (String) fieldValue; value = AesUtil.decrypt(value); field.set(object, value); encryptField.set(object, null); } } } private static List<Field> findEncryptTagFields(Object object) { Class clazz = object.getClass(); List<Field> fieldList = new ArrayList<>(); for(; clazz != null; clazz = clazz.getSuperclass()) { Field[] declaredFields = clazz.getDeclaredFields(); int length = declaredFields.length; for(int index = 0; index < length; ++index) { Field field = declaredFields[index]; if (field.getAnnotation(EncryptTag.class) != null) { if (field.getType() == String.class) { field.setAccessible(true); fieldList.add(field); } else { LOGGER.error("@EncryptTag should be used on String field. class: {}, fieldName: {}", clazz.getName(), field.getName());} } } } return fieldList; } private static Field getEncryptField(Object object, Field field) throws Exception { String encryptFieldName = AesUtil.encrypt(field.getName()); Field encyptField = getField(object, encryptFieldName); if (encyptField == null) { thrownew Exception(object.getClass() + "对象没有对应的加密字段:" + encryptFieldName); } else { encyptField.setAccessible(true); return encyptField; } } public static Field getField(Object object, String fieldName) { for(Class clazz = object.getClass(); clazz != null; clazz = clazz.getSuperclass()) { Field[] var3 = clazz.getDeclaredFields(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Field field = var3[var5]; if (field.getName().equals(fieldName)) { return field; } } } returnnull; } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.日志脱敏方案
日志脱敏,核心在于序列化时对于敏感字段修改其序列化方式。各大序列化工具一般都有序列化自定义功能,关注公众号:码猿技术专栏,回复关键词:IDEA 获取最新版IDEA破解脚本!本文以fastjson为例讲解实现,实现方式有两种:
基于注解@JSONField实现基于序列化过滤器@JSONField方式不建议使用,对业务入侵太大。另外一种继续序列化过滤器,fastjson提供了多种SerializeFilter:
PropertyPreFilter 根据PropertyName判断是否序列化PropertyFilter 根据PropertyName和PropertyValue来判断是否序列化NameFilter 修改Key,如果需要修改Key,process返回值则可ValueFilter 修改ValueBeforeFilter 序列化时在最前添加内容AfterFilter 序列化时在最后添加内容通过实现ValueFilter自定义序列化扩展,针对目标类以及字段进行脱敏返回。
核心代码简化如下:
复制public class FastjsonValueFilter implements ValueFilter { @Override public Object process(Object object, String name, Object value) { if (needDesensitize(object, name)) { return desensitize(value); } } } String s = JSON.toJSONString(new Person("131xxxx1552","123@163.com"),new FastjsonValueFilter());1.2.3.4.5.6.7.8.9.10.在标记脱敏字段以及对应方法时,可以通过配置的方法, 对类相关的脱敏字段以及方法进行封装。要求不高的话添加响应的注解也可实现。
输出脱敏
在输出层织入切面进行拦截,在切面内实现脱敏逻辑。实现逻辑跟日志脱敏类似,需要对脱敏字段进行标记以及对应脱敏方法。
如果是Spring Boot集成,配置 Spring MVC 的话只需继承 WebMvcConfigurer 覆写 configureMessageConverters方法,支持全局和指定类脱敏配置,示例如下:
复制@Configuration publicclass FastJsonWebSerializationConfiguration implements WebMvcConfigurer { @Bean(name = "httpMessageConverters") public HttpMessageConverters fastJsonHttpMessageConverters() { // 1.定义一个converters转换消息的对象 FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); // 2.添加fastjson的配置信息,比如: 是否需要格式化返回的json数据 FastJsonConfig fastJsonConfig = new FastJsonConfig(); fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); // 中文乱码解决方案 List<MediaType> mediaTypes = new ArrayList<>(); //设定json格式且编码为UTF-8 mediaTypes.add(MediaType.APPLICATION_JSON_UTF8); fastConverter.setSupportedMediaTypes(mediaTypes); //添加全局自定义脱敏 fastJsonConfig.setSerializeFilters(new ValueDesensitizeFilter()); //添加指定类脱敏方法 Map<Class<?>, SerializeFilter> classSerializeFilters = new HashMap<>(); classSerializeFilters.put(Employee.class, new FastjsonValueFilter()); fastJsonConfig.setClassSerializeFilters(classSerializeFilters); // 3.在converter中添加配置信息 fastConverter.setFastJsonConfig(fastJsonConfig); // 4.将converter赋值给HttpMessageConverter HttpMessageConverter<?> converter = fastConverter; // 5.返回HttpMessageConverters对象 returnnew HttpMessageConverters(converter); } }1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.总结
本文总结了企业中脱敏方案实现,包含数据库脱敏、日志脱敏、输出脱敏,并贴上关键实现代码。能够满足业务的要求。
相关文章
深入了解飞行堡垒BIOS教程(掌握飞行堡垒BIOS设置技巧,保障电脑安全和性能)
摘要:随着电脑技术的不断发展,飞行堡垒BIOS作为一种重要的系统设置工具,对电脑的安全性和性能起着至关重要的作用。本文将以飞行堡垒BIOS教程为主题,通过详细介绍和解读,帮助读者更好地了...2025-11-05- 复制select*fromtablenamewheretime>= to_date(2011-05-02,yyyy-mm-dd) andtime&2025-11-05

为什么API网关不足以保证API安全?API安全之路指向何处
云计算架构的出现使企业重新思考应用程序的扩展方式,从而推动了企业摆脱通过虚拟机等基础设施部署全栈应用程序,而是通过创建由多个互操作服务组成的API,采用微服务方法。根据Gartner的预测,到20232025-11-05
互联网上存在很多开源情报Open-Source Intelligence,OSINT)信息,其中开源是指公众容易获取和分发的信息或程序;情报是获取和应用知识的能力。总的来说,开源情报就是从公开来源获取2025-11-05- 摘要:在使用拯救者电脑的过程中,由于各种原因,我们可能会遇到一些BIOS版本不兼容、无法正常启动等问题。为了解决这些问题,我们需要学习如何进行BIOS降级。本文将为大家详细介绍拯救者BI...2025-11-05
Windows7的DLL侧载缺陷近期被QBot恶意软件利用,攻击者通过Windows计算器在受感染的计算机上侧载恶意的有效载荷。QBotQakbot)最初以银行软件木马起家,目前已经发展成为针对Win2025-11-05

最新评论