数据加密实现

实现思路

  1. 自定义脱敏的注释,包含:脱敏实类和脱敏字段
  2. 使用拦截器在数据入库前加密
  3. 使用拦截器在数据出库时解密
  4. 可以使用mybatis的配置,配置两个插件,去引入自定义的拦截器,来处理数据加密和解密操作

数据脱敏演示

  1. 下载源代码,github源码链接
  2. 初始化sql
  3. 修改配置文件的数据库连接配置
  4. 运行com.ocean.angel.tool.controller.UserInfoControllerTest.save()方法,入库数据如下图:
    在这里插入图片描述
  5. 运行com.ocean.angel.tool.controller.UserInfoControllerTest.get(),出库数据,截图如下:
    在这里插入图片描述

使用指南

  1. 在返回的实体类上添加注解@SensitiveBean
  2. 在返回的实体类上的加密字段添加@SensitiveField注解
  3. 运行注解方法,去数据库查询查看插入的数据

关键代码

  1. mybatis配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

	<settings>
		<setting name="cacheEnabled"             value="true" />  <!-- 全局映射器启用缓存 -->
		<setting name="useGeneratedKeys"         value="true" />  <!-- 允许 JDBC 支持自动生成主键 -->
		<setting name="defaultExecutorType"      value="REUSE" /> <!-- 配置默认的执行器 -->
		<setting name="logImpl"                  value="SLF4J" /> <!-- 指定 MyBatis 所用日志的具体实现 -->
		<!-- <setting name="mapUnderscoreToCamelCase" value="true"/>  驼峰式命名 -->
	</settings>

	<plugins>
		<plugin interceptor="com.ocean.angel.tool.interceptor.DecryptInterceptor"></plugin>
		<plugin interceptor="com.ocean.angel.tool.interceptor.EncryptInterceptor"></plugin>
	</plugins>

</configuration>

  1. 数据加密拦截器
package com.ocean.angel.tool.interceptor;

import com.ocean.angel.tool.annotation.SensitiveBean;
import com.ocean.angel.tool.annotation.SensitiveField;
import com.ocean.angel.tool.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.PreparedStatement;
import java.util.Objects;
import java.util.Properties;

@Slf4j
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class),
})
public class EncryptInterceptor 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();

            // 校验该实例的类是否被@SensitiveData所注解
            SensitiveBean sensitiveData = AnnotationUtils.findAnnotation(parameterObjectClass, SensitiveBean.class);

            if (Objects.nonNull(sensitiveData)) {
                // 取出当前当前类所有字段,传入加密方法
                Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                encrypt(declaredFields, parameterObject);
            }
        }
        return invocation.proceed();
    }

    /**
     * 切记配置,否则当前拦截器不会加入拦截器链
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }

    // 自定义配置写入,没有自定义配置的可以直接置空此方法
    @Override
    public void setProperties(Properties properties) {
    }

    public <T> T encrypt(Field[] declaredFields, T paramsObject) throws IllegalAccessException {
        for (Field field : declaredFields) {

            // 取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);

            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(paramsObject);

                // 暂时只实现String类型的加密
                if (object instanceof String) {
                    String value = (String) object;

                    // 加密, 这里我使用自定义的AES加密工具
                    try {
                        field.set(paramsObject, AESUtil.encrypt(value));
                    } catch (IllegalArgumentException e) {
                        log.error("AES加密失败");
                    }
                }
            }
        }
        return paramsObject;
    }
}

  1. 数据解密拦截器
package com.ocean.angel.tool.interceptor;

import cn.hutool.core.collection.CollUtil;
import com.ocean.angel.tool.annotation.SensitiveBean;
import com.ocean.angel.tool.annotation.SensitiveField;
import com.ocean.angel.tool.util.AESUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.*;
import org.springframework.core.annotation.AnnotationUtils;
import java.lang.reflect.Field;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Objects;
import java.util.Properties;

@Slf4j
@Intercepts({
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
public class DecryptInterceptor 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 (!CollUtil.isEmpty(resultList) && needToDecrypt(resultList.get(0))) {
                for (Object result : resultList) {
                    // 逐一解密
                    decrypt(result);
                }
            }
            // 基于selectOne
        } else {
            if (needToDecrypt(resultObject)) {
                decrypt(resultObject);
            }
        }
        return resultObject;
    }

    private boolean needToDecrypt(Object object) {
        if (ObjectUtils.isEmpty(object)) {
            return false;
        }
        Class<?> objectClass = object.getClass();
        SensitiveBean sensitiveDean = AnnotationUtils.findAnnotation(objectClass, SensitiveBean.class);
        return Objects.nonNull(sensitiveDean);
    }


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

    @Override
    public void setProperties(Properties properties) {

    }

    public <T> T decrypt(T result) throws IllegalAccessException {

        // 取出resultType的类
        Class<?> resultClass = result.getClass();
        Field[] declaredFields = resultClass.getDeclaredFields();

        for (Field field : declaredFields) {

            // 取出所有被EncryptDecryptField注解的字段
            SensitiveField sensitiveField = field.getAnnotation(SensitiveField.class);
            if (!Objects.isNull(sensitiveField)) {
                field.setAccessible(true);
                Object object = field.get(result);

                // 只支持String的解密
                if (object instanceof String) {
                    String value = (String) object;

                    // 对注解的字段进行逐一解密
                    try {
                        field.set(result, AESUtil.decrypt(value));
                    } catch (Exception e) {
                        log.error("AES解密失败");
                    }
                }
            }
        }
        return result;
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值