java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > MyBatis 字段加解密

MyBatis实现字段加解密的实践

作者:十年培训经验的菜包

为了数据安全问题,有时候需要将部分敏感字段加密后再入库,本文主要介绍了MyBatis实现字段加解密的实践,具有一定的参考价值,感兴趣的可以了解一下

背景

互联网系统充斥着各种敏感信息,包括各种个人信息、商业信息等等。按照要求,不允许隐私信息明文存储,需要进行加密处理,防止造成隐私泄露的风险。
我司作为一个跨境电商公司,各系统中,自然免不了涉及各类敏感信息,并且对各类敏感信息的安全级别进行了划分,不同等级的加密要求级别有一定的差别。

方案

由于不同的敏感数据需要使用不同的加解密策略,在MySQL层面,无法满足需求,所以只能在应用代码层面进行实现。但需要考虑几个点

手动加解密

这是首先被提出来的方案。该方案做法是

优点:

缺点:

综上所述,该方案完全无法满足我们对方案的要求,属于最笨的方案,可以直接say no。

自动加解密

由于无法在MySQL层面实现,又希望尽可能减少对业务代码的侵入性,那么任务只能落在ORM框架或半ORM框架上。我们系统使用的是MyBatis,那么利用MyBatis的插件机制来实现自动加解密,是个不错的选择。

优点:

缺点:

实现

编码

由于敏感数据被划分成多个不同级别,各个级别使用的加解密算法不同,所以面对这种不同加密算法的场景,策略模式非常适合;

加解密策略

public interface SensitiveStrategy {

    /**
     * 加密
     */
    String encrypt(String value);

    /**
     * 解密
     */
    String decrypt(String value);
}

各种加解密算法,只要实现该接口即可,此处略。

自定义注解

自定义一个字段上的注解,目的是为了让MyBatis拦截器识别哪些字段需要加解密,加解密的策略是什么。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface SensitiveField {

    Class<? extends SensitiveStrategy> sensitiveStrategy();
}

MyBatis拦截器

我们需要定义一个MyBatis拦截器,该拦截器的作用有以下:

我们知道,MyBatis的拦截器插件,可以对四大组件Executor、StatementHandler、ParameterHandler、ResultSetHandler进行拦截,由于我们只需要对入参和结果进行拦截和修改,所以只需指定拦截ParameterHandler、ResultSetHandler即可。

@Slf4j
@Component
@Intercepts(value = {
        @Signature(type = ParameterHandler.class,method = "setParameters",args = {PreparedStatement.class}),
        @Signature(type = ResultSetHandler.class,method = "handleResultSets",args = {Statement.class})
})
public class SensitiveInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        Object target = invocation.getTarget();
        //入参加密
        if (target instanceof ParameterHandler parameterHandler){
            //获取参数对象
            Object parameterObj = ReflectUtil.getFieldValue(parameterHandler, "parameterObject");
            if (parameterObj != null){
                //获取参数对象内的字段
                Arrays.stream(ReflectUtil.getFields(parameterObj.getClass()))
                        .filter(field -> String.class.equals(field.getType()))
                        .filter(field -> field.getAnnotation(SensitiveField.class)!=null )
                        .filter(field -> ReflectUtil.getFieldValue(parameterObj,field) != null)
                        .forEach(field -> {
                            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
                            Class<? extends SensitiveStrategy> strategyClazz = sensitiveField.sensitiveStrategy();
                            if (strategyClazz != null){
                                SensitiveStrategy strategy = SpringContext.getBean(strategyClazz);
                                String encrypt = strategy.encrypt(ReflectUtil.getFieldValue(parameterObj, field).toString());
                                ReflectUtil.setFieldValue(parameterObj,field,encrypt);
                            }
                        });
                ReflectUtil.setFieldValue(parameterHandler,"parameterObject",parameterObj);
            }
        }

        Object resultObj = invocation.proceed();

        //出参解密
        if (resultObj != null && target instanceof ResultSetHandler){
            List<?> resultList = (List<?>) resultObj;
            for (Object result : resultList) {
                if (!SimpleTypeRegistry.isSimpleType(result.getClass())){
                    Arrays.stream(ReflectUtil.getFields(result.getClass()))
                            .filter(field -> String.class.equals(field.getType()))
                            .filter(field -> field.getAnnotation(SensitiveField.class)!=null )
                            .filter(field -> ReflectUtil.getFieldValue(result,field) != null)
                            .forEach(field -> {
                                SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
                                Class<? extends SensitiveStrategy> strategyClazz = sensitiveField.sensitiveStrategy();
                                if (strategyClazz != null){
                                    SensitiveStrategy strategy = SpringContext.getBean(strategyClazz);
                                    String decrypt = strategy.decrypt(ReflectUtil.getFieldValue(result, field).toString());
                                    ReflectUtil.setFieldValue(result,field,decrypt);
                                }
                            });
                }
            }
            resultObj = resultList;
        }
        return resultObj;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target,this);
    }
}

由于系统是基于SpringBoot的,所以我们将所有实现的加解密策略对象,交给Spring容器管理。在MyBatis拦截器中,直接从Spring容器中获取对应加解密策略使用接口。

上线

上线阶段,我们分为以下几个步骤实施

其他问题

其实在整个过程中,实现功能相关的编码并不复杂。除此之外,需要去识别并解决其他的一些问题,这期间花费了更多的时间,例如

敏感字段like搜索

字段加密后存储,就无法直接使用like关键字搜索。其实经过我司安全部门评估,敏感字段也不允许进行模糊搜索,有数据泄露风险,所以在产品方案层面,直接砍掉了类似功能;
若是一定要保留改功能,也可以使用以下方案

注意,like字段需要使用text类型,性能会很低。

敏感字段group by

对于一些敏感级别较低的字段,采用了固定加密方式(即多次对相同的数据进行加密后结果不变),此时由于结果不变,可以直接使用加密后的字段进行group by;

但是对于敏感级别较高的字段,我司采用了动态加密方式(即多次对相同的数据加密结果不一致),此时由于结果不一致,无法进行group by操作。解决方案的拓展多一列,对源数据使用固定加密后,将结果存进该拓展字段,group by业务使用。

到此这篇关于MyBatis实现字段加解密的实践的文章就介绍到这了,更多相关MyBatis 字段加解密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文