惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

云风的 BLOG
云风的 BLOG
量子位
H
Help Net Security
月光博客
月光博客
Last Week in AI
Last Week in AI
F
Fortinet All Blogs
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
博客园 - Franky
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
Netflix TechBlog - Medium
Vercel News
Vercel News
T
Tailwind CSS Blog
Stack Overflow Blog
Stack Overflow Blog
aimingoo的专栏
aimingoo的专栏
Martin Fowler
Martin Fowler
Apple Machine Learning Research
Apple Machine Learning Research
博客园 - 叶小钗
J
Java Code Geeks
IT之家
IT之家
P
Proofpoint News Feed
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
小众软件
小众软件
Engineering at Meta
Engineering at Meta
U
Unit 42
F
Full Disclosure
B
Blog
The Hacker News
The Hacker News
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Simon Willison's Weblog
Simon Willison's Weblog
Microsoft Security Blog
Microsoft Security Blog
Cyberwarzone
Cyberwarzone
V
V2EX
C
CERT Recently Published Vulnerability Notes
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Spread Privacy
Spread Privacy
Jina AI
Jina AI
GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
Blog — PlanetScale
Blog — PlanetScale
Know Your Adversary
Know Your Adversary
美团技术团队
罗磊的独立博客
Scott Helme
Scott Helme
Hugging Face - Blog
Hugging Face - Blog
S
Schneier on Security
Google DeepMind News
Google DeepMind News
人人都是产品经理
人人都是产品经理

博客园 - Johnson_wang

对云服务器挂载cos桶 记录一次xxlJob升级版本导致的 xxl-job remoting error(Connection reset) 记一次NoClassDeffoundEror问题解决过程 nginx配置支持ws,并解决跨域 关于消费端接入dubbo,连接失败问题 RedisTemplate关于key出现前缀\xac\xed\x00\x05t\x00\x0f - Johnson_wang - 博客园 ES 单索引大表拆分 关于ES索引被聚合查询导致filedata堵塞 (pressure too high, (smooth) bulk request circuit break) 发送HTML格式邮件 elasticsearch报错FORBIDDEN/12/index read-only / allow delete spring Boot 相关问题以及修复(后续待补充) 队列缓存区-db写入 synchronized的实现原理 Scheduler踩坑记录 关于RedisTemplate的map存储踩坑记录 关于HashMap的加载因子相关理解 Mybatis 分页插件PageHelper 遇坑 Linux 下 Mysql忘记密码重置 Eclipse MAT和jvisualvm分析内存溢出
mybatis数据加解密处理方案
Johnson_wang · 2022-01-07 · via 博客园 - Johnson_wang

1.背景

  为了防止数据库的用户数据安全,所以需要对用户数据进行加密,具体为插入数据进行加密,查询数据自动解密。

2.方案

  查询相关文档后,发现mybatis有2种方案可以处理:

   a.使用typeHandler

   b.使用intercept

   经过对批量数据执行后,发现千、万、百万级别数据拦截器相对更快一些。

3.具体实现

  3.1 intercept

   a.注解

   EncryptDecryptData 该注解用于标记拦截器适用的DBEntity

@Inherited
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptData {
}

EncryptDecryptData

  EncryptDecryptField 该注解用于标记拦截器用于加密的字段

@Inherited
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}

EncryptDecryptField

  b.加密方法

import java.lang.reflect.Field;
import java.util.Objects;

public class  EncryptDecrypt {

    private final static String key = "asffqqas";
    /**
     * 加密
     *
     * @param declaredFields paramsObject所声明的字段
     * @param paramsObject   mapper中paramsType的实例
     * @return T
     * @throws IllegalAccessException 字段不可访问异常
     */
    public static <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);
                //暂时只实现String类型的加密
                if (object instanceof String) {
                    String value = (String) object;
                    //加密  Des加密工具
                    field.set(paramsObject, DesUtil.encrypt(value,key));
                }
            }
        }
        return paramsObject;
    }
    /**
     * 解密
     *
     * @param result resultType的实例
     * @return T
     * @throws IllegalAccessException 字段不可访问异常
     */
    public static <T> T decrypt(T result) throws IllegalAccessException {
        //取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //取出所有被EncryptDecryptField注解的字段
            EncryptDecryptField sensitiveField = field.getAnnotation(EncryptDecryptField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);
                //只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;
                    //对注解的字段进行逐一解密
                    field.set(result, DesUtil.decrypt(value,key));
                }
            }
        }
        return result;
    }
}

EncryptDecrypt

  c.拦截器

写入数据进行加密(insert)

@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class WriteInterceptor implements Interceptor {


    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       //@Signature 指定了 type= parameterHandler 后,这里的 invocation.getTarget() 便是parameterHandler
        //若指定ResultSetHandler ,这里则能强转为ResultSetHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
        // 获取参数对像,即 mapper 中 paramsType 的实例
        Field parameterField = parameterHandler.getClass().getDeclaredField("parameterObject");
        parameterField.setAccessible(true);

        //取出实例
        Object parameterObject = parameterField.get(parameterHandler);
        if (parameterObject != null) {
            Class<?> parameterObjectClass = parameterObject.getClass();
            //校验该实例的类是否被@EncryptDecryptData所注解
            EncryptDecryptData encryptDecryptData = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptData.class);
            if (Objects.nonNull(encryptDecryptData)) {
                //取出当前当前类所有字段,传入加密方法
                Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                EncryptDecrypt.encrypt(declaredFields, parameterObject);
            }

        }
        return invocation.proceed();
    }
    @Override
    public Object plugin(Object o) {
        //这里必须写入,会判定是否把当前拦截器启动
        return Plugin.wrap(o, this);
    }

}

View Code

 查询数据进行解密

@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class ReadInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //取出查询的结果
        Object resultObject = invocation.proceed();
        if (Objects.isNull(resultObject)) {
            return null;
        }
        //基于selectList
        if (resultObject instanceof ArrayList) {
            ArrayList resultList = (ArrayList) resultObject;
            if (!CollectionUtils.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    //逐一解密
                    EncryptDecrypt.decrypt(result);
                }
            }
            //基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                EncryptDecrypt.decrypt(resultObject);
            }
        }
        return resultObject;
    }

    private boolean needToDecrypt(Object object) {
        Class<?> objectClass = object.getClass();
        EncryptDecryptData sensitiveData = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptData.class);
        return Objects.nonNull(sensitiveData);
    }


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

View Code

@Intercepts mybatis的注解,用于标记这是一个拦截器,@Signature则表明要拦截的接口、方法以及对应的参数类型,主要类型有如下:
type method 备注
Executor update, query, flushStatements, commit, rollback, getTransaction, close, isClosed 拦截执行器的方法
ParameterHandler getParameterObject, setParameters 拦截参数的处理
ResultSetHandler handleResultSets, handleOutputParameters 拦截结果集的处理
StatementHandler prepare, parameterize, batch, update, query 拦截Sql语法构建的处理

 d.使用注解

@Data
@TableName("user")
@EncryptDecryptData
public class UserDBEntity implements Serializable {

    @TableId(value = "user_id",type = IdType.AUTO)
    private Integer userId;


    @EncryptDecryptField
    @TableField("address")
    private String address;

    @EncryptDecryptField
    @TableField("mobile")
    private String mobile;

    

}

View Code

 e.注册拦截

  在生成的sqlSessionFactory中加入拦截器

@Bean(name = "userSqlSessionFactory")
    public SqlSessionFactory userSqlSessionFactory(@Qualifier("userDB") DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
        bean.setDataSource(dataSource);
        
        //添加插件
        bean.setPlugins(new Interceptor[]{new WriteInterceptor(),new ReadInterceptor()});

       //省略代码
        return bean.getObject();
    }

View Code

 f.使用

 使用拦截器时,因为注解是写在UserDBEntity上,所以插入或查询数据时,要传入UserDBEntity对象,例如:

 1 @Repository
 2 public interface UserMapper {
 3 
 4     @Select("select * from user where mobile = #{mobile} ")
 5     UserDBEntity selectByMobile(TestAddressDBEntity mobile);
 6 
 7 
 8     @Insert(" insert into user (user_id,mobile,address) values (#{userId},#{mobile},#{address})")
 9     int insert(UserDBEntity userInfo);
10      
11 
12 }
13 
14 /*UserDBEntity query = new UserDBEntity();
15         UserDBEntity.setMobile(13711111111);
16         UserDBEntity s =  UserMapper.selectByMobile(query);*/

View Code