参考文章:基于MyBatis-Plus数据库存取字段加解密解法两种_数据库_虫叽叽-华为开发者空间

一、思路分析

         最终实现的效果:新增数据的时候数据库表字段存储密文,查询的时候展示明文;采用SM4对称加密方式进行加解密,因为SM4可逆。

        首先,mybatis的原理如下图:

 

        由图可见,ParameterHandler是设置编译参数,最后保存到数据库中,ResultSetHandler是用来处理最终查询结果。

二、具体实现方法

         1、自定义注解

import java.lang.annotation.*;


/**
 * 自定义类注解
 */
@Documented
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptClass {
}
/**
 * 自定义字段注解
 */
@Documented
@Inherited
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptDecryptField {
}

 2、引用注解位置

        注解需要放在映射数据库表的实体类中

@Data
@TableName(value = "tb_user", autoResultMap = true)
@EncryptDecryptClass
public class TbUser {

    /**
     * 主键
     */
    @TableId(type = IdType.ASSIGN_ID)
    private String id;

    /**
     * 用户名
     */
    @EncryptDecryptField
    @TableField(value = "username")
    private String username;

    /**
     * 用户手机号码
     */
    @TableField("role")
    private String role;

}

注意:autoResultMap = true是必须存在的

3、设置拦截器

/**
 * 设置入参拦截器(ParameterHandler)
 */

@Intercepts({
        @Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Slf4j
public class MybatisParameterIntercept implements Interceptor {

    @Autowired
    private LocalPathConfig localPathConfig;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //拦截 ParameterHandler 的 setParameters 方法 动态设置参数
        if (invocation.getTarget() instanceof ParameterHandler) {
            //注意ParameterHandler中的参数类型是实体类还是Map
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

            Object parameterObject = parameterHandler.getParameterObject();
            if (parameterObject instanceof Map){
                Map<String,Object> paramMap = (Map<String, Object>) parameterObject;
                // 遍历 ParamMap 以找到所有实体类的参数
                for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                    //此处的user对应到mapper中注解Param的定义
                    if (entry.getKey().equals("user")) {
                        Object paramValue = entry.getValue();
                        // 检查参数值是否为实体类实例,TbUser是自己定义的实体类名
                        if (paramValue instanceof TbUser ) {
                            Class<?> parameterObjectClass = paramValue.getClass();
                            EncryptDecryptClass encryptDecryptClass = parameterObjectClass.getAnnotation(EncryptDecryptClass.class);
                            if (encryptDecryptClass != null) {
                                Field[] declaredFields = parameterObjectClass.getDeclaredFields();
                                final Object encrypt = EncryptDecryptUtils.encrypt(declaredFields, paramValue,localPathConfig.getEncDecrySm4Key());
                            }
                        }
                    }
                }
            }else {
                if (Objects.nonNull(parameterObject)){
                    Class<?> parameterObjectClass = parameterObject.getClass();
                    EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(parameterObjectClass, EncryptDecryptClass.class);
                    if (Objects.nonNull(encryptDecryptClass)){
                        Field[] declaredFields = parameterObjectClass.getDeclaredFields();

                        final Object encrypt = EncryptDecryptUtils.encrypt(declaredFields, parameterObject,localPathConfig.getEncDecrySm4Key());
                    }
                }
            }
        }
        return invocation.proceed();
    }
}
/**
 * 出参拦截器(ResultSetHandler)
 */@Intercepts({
        @Signature(type = ResultSetHandler.class,method = "handleResultSets",args = Statement.class)
})
@ConditionalOnProperty(value = "domain.encrypt",havingValue = "true")
@Component
@Slf4j
public class MybatisResultSetIntercept implements Interceptor {

    @Autowired
    private LocalPathConfig localPathConfig;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object result = invocation.proceed();
        if (Objects.isNull(result)){
            return null;
        }

        if (result instanceof ArrayList) {
            ArrayList resultList = (ArrayList) result;
            if (CollectionUtils.isNotEmpty(resultList) && needToDecrypt(resultList.get(0))){
                for (int i = 0; i < resultList.size(); i++) {
                    EncryptDecryptUtils.decrypt(resultList.get(i),localPathConfig.getEncDecrySm4Key());
                }
            }
        }else {
            if (needToDecrypt(result)){
                EncryptDecryptUtils.decrypt(result,localPathConfig.getEncDecrySm4Key());
            }
        }
        return result;
    }

    private boolean needToDecrypt(Object object){
        Class<?> objectClass = object.getClass();
        EncryptDecryptClass encryptDecryptClass = AnnotationUtils.findAnnotation(objectClass, EncryptDecryptClass.class);
        if (Objects.nonNull(encryptDecryptClass)){
            return true;
        }
        return false;
    }
}

4、注解拦截器

/**
 * 注册入参和出参拦截器
 */
@Configuration
@MapperScan("com.jtkj.mapper" )
public class MyBatisPlusConfig {

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 注册MyBatis拦截器
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    public String myInterceptor(SqlSessionFactory sqlSessionFactory){

        sqlSessionFactory.getConfiguration().addInterceptor(parameterIntercept());

        sqlSessionFactory.getConfiguration().addInterceptor(resultSetIntercept());

        return "myInterceptor";
    }

    public MybatisParameterIntercept parameterIntercept(){
        return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisParameterIntercept.class);
    }

    public MybatisResultSetIntercept resultSetIntercept(){
        return applicationContext.getAutowireCapableBeanFactory().createBean(MybatisResultSetIntercept.class);
    }
}

5、加解密工具类

/**
 * 数据库字段加解密工具类
 */
public class EncryptDecryptUtils {

    /**
     * 多field加密方法
     *
     * @param declaredFields
     * @param parameterObject
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public static <T> T encrypt(Field[] declaredFields, T parameterObject,String key) throws IllegalAccessException {
        for (Field field : declaredFields) {
            EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
            if (Objects.isNull(annotation)) {
                continue;
            }
            encrypt(field, parameterObject,key);
        }
        return parameterObject;
    }


    /**
     * 单个field加密方法
     *
     * @param field
     * @param parameterObject
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public static <T> T encrypt(Field field, T parameterObject,String key) throws IllegalAccessException {
        field.setAccessible(true);
        Object object = field.get(parameterObject);
        if (object instanceof BigDecimal) {
            BigDecimal value = (BigDecimal) object;
            long longValue = value.movePointRight(4).subtract(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).longValue();
            field.set(parameterObject, BigDecimal.valueOf(longValue));
        } else if (object instanceof Integer) {
            //加密业务
        } else if (object instanceof Long) {

        } else if (object instanceof String) {
            //定制String类型的加密算法
            String value = (String) object;
            field.set(parameterObject, new SM4Utils(key).encryptData_ECB(value));
        }
        return parameterObject;
    }

    /**
     * 解密方法
     *
     * @param result
     * @param <T>
     * @return
     * @throws IllegalAccessException
     */
    public static <T> T decrypt(T result,String key) throws IllegalAccessException {
        Class<?> parameterObjectClass = result.getClass();
        Field[] declaredFields = parameterObjectClass.getDeclaredFields();
        decrypt(declaredFields, result,key);
        return result;
    }

    /**
     * 多个field解密方法
     *
     * @param declaredFields
     * @param result
     * @throws IllegalAccessException
     */
    public static void decrypt(Field[] declaredFields, Object result,String key) throws IllegalAccessException {
        for (Field field : declaredFields) {
            EncryptDecryptField annotation = field.getAnnotation(EncryptDecryptField.class);
            if (Objects.isNull(annotation)) {
                continue;
            }
            decrypt(field, result,key);
        }
    }

    /**
     * 单个field解密方法
     *
     * @param field
     * @param result
     * @throws IllegalAccessException
     */
    public static void decrypt(Field field, Object result,String key) throws IllegalAccessException {
        field.setAccessible(true);
        Object object = field.get(result);
        if (object instanceof BigDecimal) {
            BigDecimal value = (BigDecimal) object;
            double doubleValue = value.add(BigDecimal.valueOf(Integer.MAX_VALUE >> 3)).movePointLeft(4).doubleValue();
            field.set(result, BigDecimal.valueOf(doubleValue));
        } else if (object instanceof Integer) {

        } else if (object instanceof Long) {

        } else if (object instanceof String) {
            //定制String类型的解密算法
            String value = (String) object;
            String decData = new SM4Utils(key).decryptData_ECB(value);
            if (StringUtil.isNotEmpty(decData)){
                field.set(result, decData);
            }else {
                field.set(result,value );
            }


        }
    }


}

三、实例

1、controller

@RestController
@Api(tags = "test", value = "tets")
@RequestMapping("/test/")
public class TestController {

    @Autowired
    private TestService testService;

    /**
     * 新增
     */
    @RequestMapping("add")
    public ActionResult add(@RequestBody TbUser user){
        testService.add(user);
        return ActionResult.success();
    }
    @GetMapping("list")
    public ActionResult list(){
        return ActionResult.success(testService.getList());
    }

    /**
     * 新增
     */
    @RequestMapping("add1")
    public ActionResult add1(@RequestBody TbUser user){
        testService.create(user);
        return ActionResult.success();
    }

    @GetMapping("list1")
    public ActionResult list1(){
        return ActionResult.success(testService.getList1());
    }
}

2、service

public interface TestService {
    void add(TbUser user);

    List<TbUser> getList();

    void create(TbUser user);

    List<TbUser> getList1();
}
@Service
public class TestServiceImpl extends ServiceImpl<TestMapper, TbUser> implements TestService {

    @Autowired
    TestMapper testMapper;

    @Override
    public void add(TbUser user) {
        String username = user.getUsername();
        String role = user.getRole();
        user.setId(RandomUtil.uuId());
        testMapper.add(user);
    }

    @Override
    public List<TbUser> getList() {
        return testMapper.getList();
    }

    @Override
    public void create(TbUser user) {
        user.setId(RandomUtil.uuId());
        this.save(user);
    }

    @Override
    public List<TbUser> getList1() {
        return this.list();
    }
}

3、mapper

public interface TestMapper extends BaseMapper<TbUser> {

}

四、效果展示

五、总结

        使用mybatis拦截器方式复用性强,比较灵活,还有一种方式是在mybatis的xml文件中进行加解密处理,需要手写sql,比较麻烦没有贴出来。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐