Hibernate3加解密

一、前言

客户要求系统的敏感字段需要使用国密SM4算法进行加密,需要在数据库中看到加密的数据。因为平台的持久层使用的是Hibernate,因此利用hibernate的拦截器在数据读取时进行解密,在数据进行持久化时进行加密实现。

二、实现思路

1、敏感实体类上添加加密注解,可以通过注解区分出哪些实体类需要加密

2、敏感实体类的字段也需要增加加密注解,用于区分哪些字段需要加密

3、利用在Hibernate的拦截器EmptyInterceptor的对应事件,通过反射获取需要处理的实体类和字段,在数据入库前在拦截器对应的方法对数据进行加密处理,在数据读取时在拦截器中对数据进行解密处理。

4、以上处理当存储之后加密字段已修改成加密,需要重新解密,则利用Hibernate的监听器PostInsertEventListener的对应事件,通过反射获取需要处理的实体类和字段,在存储成功后在进行解密(若保存后无需使用该对象时可不写监听器)。

三、Hibernate拦截器简介

Hibernate定义了一个拦截器,位于org.hibernate.Interceptor,提供了一系列的拦截器方法。详细可见Hibernate官网文档

public class EmptyInterceptor implements Interceptor, Serializable {
    public static final Interceptor INSTANCE = new EmptyInterceptor();

    protected EmptyInterceptor() {
    }

    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
    }

    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        return false;
    }

    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        return false;
    }

    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        return false;
    }

    public void postFlush(Iterator entities) {
    }

    public void preFlush(Iterator entities) {
    }

    public Boolean isTransient(Object entity) {
        return null;
    }

    public Object instantiate(String entityName, EntityMode entityMode, Serializable id) {
        return null;
    }

    public int[] findDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        return null;
    }
    ...

}

这里只需要用到三个方法,分别是onLoad初始化前调用、onSave保存前调用和onFlushDirty更新对象前调用。需要注意的是onSave的方法并不是指保存时调用,而是指Hibernate执行insert操作时才会调用,而update操作对应的拦截方法是onFlushDirty

方法名

描述

onLoad

在初始化对象之前调用。拦截器可能会更改状态,该状态将被传播到持久对象。请注意,当调用此方法时,实体将是该类的一个未初始化的空实例。

onSave

在保存对象之前调用。拦截器可以修改状态,该状态将用于SQL插入并传播到持久对象。

onFlushDirty

在冲洗过程中检测到对象变脏时调用。拦截器可以修改检测到的currentState,它将被传播到数据库和持久对象。请注意,并非所有刷新都以与数据库的实际同步结束,在这种情况下,新的currentState将传播到对象,但不一定(立即)传播到数据库。强烈建议拦截器不要修改以前的状态。

Hibernate监听器简介

hibernate的监听器定义,不需要太复杂的代码,不需要继承或实现某个接口,仅通过注解就能实现,如前面使用的@PrePersist,@PreUpdate,@PostLoad等。常用注解列举如下:

注解

使用

@PrePersist

在插入之前调用

@PostPersist

在插入之后调用

@PerUpdate

在更新之前调用

@PostUpdate

在更新之后调用

@PerRemove

在删除之前调用

@PostRemove

在删除之后调用

@PostLoad

在查询之后调用(转换对象之前)

实现方式

自定义加解密标记注解

标记实体类是否需要进行加解密注解

import java.lang.annotation.*;

/**
 * 需要加解密的表注解,只有添加此注解的表才需要进行加解密
 */
@Target(ElementType.TYPE)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface EncryptTable {

}

标记实体类中的字段是否需要进行加解密处理注解

import java.lang.annotation.*;

/**
 * 加解密表字段,只有添加了此注解的实体类字段才要进行加解密
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface EncryptField {

}

将注解加到实体类上

@Data
@Entity
@Table(name = UserInfo.TABLE_NAME)
@EncryptTable
public class UserInfo {

    public static final String TABLE_NAME = "user_info";

    /**
     * 主键
     */
    @Id
    @Column(name = "RID")
    private String rid;

    /**
     * 用户名
     */
    @EncryptField
    @Column(name = "user_name")
    private String username;

    /**
     * 密码
     */
    private String password;

    /**
     * 昵称
     */
    @EncryptField
    @Column(name = "NICKNAME")
    private String nickname;

    /**
     * 学历
     */
    @EncryptField
    private String education;

}

实现拦截器

import com.choy.demo.encrypt.annotation.EncryptField;
import com.choy.demo.encrypt.annotation.EncryptTable;
import com.choy.demo.utils.RSAEncryptUtils;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * hibernate加解密拦截器
 */
@Component
public class EncryptInterceptor extends EmptyInterceptor {

    private final static Logger LOGGER = LoggerFactory.getLogger(EncryptInterceptor.class);

    /**
     * 更新时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param currentState  当前实体类对应的值
     * @param previousState 修改前实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
        Object[] newState = dealField(entity, currentState, propertyNames, "onFlushDirty");
        return super.onFlushDirty(entity, id, newState, previousState, propertyNames, types);
    }

    /**
     * 加载时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param state         实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        Object[] newState = dealField(entity, state, propertyNames, "onLoad");
        return super.onLoad(entity, id, newState, propertyNames, types);
    }

    /**
     * 保存时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param state         实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        Object[] newState = dealField(entity, state, propertyNames, "onSave");
        return super.onSave(entity, id, newState, propertyNames, types);
    }


    /**
     * 处理字段对应的数据
     *
     * @param entity        实体类
     * @param state         数据
     * @param propertyNames 字段名称
     * @return 解密后的字段名称
     */
    private Object[] dealField(Object entity, Object[] state, String[] propertyNames, String type) {
        List<String> annotationFields = getAnnotationField(entity);
        LOGGER.info("调用方法:{}, 需要加密的字段:{}", type, annotationFields);
        // 遍历字段名和加解密字段名
        for (String aField : annotationFields) {
            for (int i = 0; i < propertyNames.length; i++) {
                if (!propertyNames[i].equals(aField)) {
                    continue;
                }
                // 如果字段名和加解密字段名对应且不为null或空
                if (state[i] == null || Objects.equals(state[i].toString(), "")) {
                    continue;
                }
                if ("onSave".equals(type) || "onFlushDirty".equals(type)) {
                    LOGGER.info("当前字段:{}, 加密前:{}", aField, state[i]);
                    //调用加密方法
                    state[i] =xxx(state[i].toString());
                    LOGGER.info("当前字段:{}, 加密后:{}", aField, state[i]);
                } else if ("onLoad".equals(type)) {
                    LOGGER.info("当前字段:{}, 解密前:{}", aField, state[i]);
                    //调用解密方法
                    state[i] = xxx(state[i].toString());
                    LOGGER.info("当前字段:{}, 解密后:{}", aField, state[i]);
                }
            }
        }
        return state;
    }


    /**
     * 获取实体类中带有注解EncryptField的变量名
     *
     * @param entity 实体类
     * @return 需要加解密的字段
     */
    private List<String> getAnnotationField(Object entity) {
        // 判断当前实体类是否有加解密注解
        Class<?> entityClass = entity.getClass();
        if (!entityClass.isAnnotationPresent(EncryptTable.class)) {
            return Collections.emptyList();
        }
        List<String> fields = new ArrayList<>();
        // 获取实体类下的所有成员并判断是否存在加解密注解
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            EncryptField encryptField = field.getAnnotation(EncryptField.class);
            if (Objects.isNull(encryptField)) {
                continue;
            }
            fields.add(field.getName());
        }
        return fields;
    }

}

实现监听器

import com.chis.common.utils.CacheUtils;
import com.chis.common.utils.StringUtils;
import com.chis.common.utils.sm.Sm4Util;
import com.chis.modules.system.encrypt.annotation.EncryptField;
import com.chis.modules.system.encrypt.annotation.EncryptTable;
import org.hibernate.event.PostInsertEvent;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.def.DefaultLoadEventListener;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
 *  <p>类描述:hibernate保存之后调用 监听器</p>
 * @ClassAuthor  2023-01-14 8:52
 */
public class EncryptListener extends DefaultLoadEventListener implements PostInsertEventListener {
    @Override
    public void onPostInsert(PostInsertEvent postInsertEvent) {
       try {
           //是否需要加解密
           String infoEncryKey = CacheUtils.get("ENCRYKEY","infoEncryKey") == null ? null : CacheUtils.get("ENCRYKEY","infoEncryKey").toString();
           if(StringUtils.isBlank(infoEncryKey)){
               return;
           }
           Object entity = postInsertEvent.getEntity();
           List<String> annotationFields = getAnnotationField(entity);
           if(CollectionUtils.isEmpty(annotationFields)){
               return ;
           }
           for (String aField : annotationFields) {
               Class<?> cla = entity.getClass();
               Field[] fields = cla.getDeclaredFields();
               for (Field field : fields) {
                   field.setAccessible(true);
                   String keyName = field.getName();
                   Object value = field.get(entity);
                   if(keyName.equals(aField)){
                       //解密
                       field.set(entity,xxx(value.toString(),infoEncryKey));
                   }
               }
           }
       }catch (Exception e){
           e.printStackTrace();
       }
    }
    private List<String> getAnnotationField(Object entity) {
        // 判断当前实体类是否有加解密注解
        Class<?> entityClass = entity.getClass();
        if (!entityClass.isAnnotationPresent(EncryptTable.class)) {
            return Collections.emptyList();
        }
        List<String> fields = new ArrayList<>();
        // 获取实体类下的所有成员并判断是否存在加解密注解
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            EncryptField encryptField = field.getAnnotation(EncryptField.class);
            if (null == encryptField) {
                continue;
            }
            fields.add(field.getName());
        }
        return fields;
    }
}

还需要在persistence.xml配置文件中添加配置(不同的框架配置不一样)

    <property name="hibernate.ejb.interceptor"
                      value="xxx.EncryptInterceptor" />
            <property name="hibernate.ejb.event.post-insert"
                      value="xxx.EncryptListener" />

sql加解密通用util

import com.chis.common.utils.CacheUtils;
import com.chis.common.utils.StringUtils;
import com.chis.common.utils.sm.Sm4Util;

/**
 *  <p>类描述: 加解密字段单独处理-只适合精确查询</p>
 * @ClassAuthor  2023-01-14 9:10
 */
public class EncryptFieldUtil {
    /**
     *  <p>方法描述:加密</p>
     * @MethodAuthor  2023-01-14 9:13
     */
    public static String strEncode(String str) {
        String infoEncryKey = "";
        if(StringUtils.isNotBlank(infoEncryKey)){
            return  xxx(str,infoEncryKey);
        }
        return str;
    }
    /**
     *  <p>方法描述:解密</p>
     * @MethodAuthor  2023-01-14 9:13
     */
    public static String strDecode(String str) {
         String infoEncryKey = "";
        if(StringUtils.isNotBlank(infoEncryKey)){
            return  xxx(str,infoEncryKey);
        }
        return str;
    }
}

小结

这种思路其实不太具有通用性,特别是如果代码中有使用原生sql方式的话,处理会比较麻烦,但如果只是个别实体类的敏感字段需要加密解密处理的话,是比较方便的处理方式。

如果您能够使用JPA2.1,我建议在AttributeConverter实现中使用@Convert注释。

AttributeConverter定义了实体属性在序列化到数据存储时和从数据存储反序列化时的状态之间的协定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
针对这个需求,可以考虑以下方案: 1. 定义一个加解密工具类,封装AES加密和解密的方法,供程序调用。这样可以统一管理加解密的实现,提高代码复用性和可维护性。 2. 对于查询操作,可以在Java程序中使用Hibernate或JDBC查询数据时,在获取敏感信息字段值后,调用加解密工具类进行解密,然后再将解密后的明文结果返回给调用方。 3. 对于保存或更新操作,可以在进行操作前,先对敏感信息字段进行加密,然后再将加密后的密文数据存入数据库中。在Java程序中,可以在使用Hibernate或JDBC进行数据存储时,调用加解密工具类进行加密处理,然后再将加密后的密文数据存入数据库中。 4. 为了保证程序的安全性,可以在程序启动时,从配置文件中读取密钥等敏感信息,然后将其存入内存中,供加解密工具类使用。同时,需要注意密钥的保护和管理,避免泄露和丢失。 5. 如果需要支持多种组件,可以将加解密工具类封装成jar包或公共模块,供其他组件调用。这样可以避免重复开发,提高代码的复用性和可维护性。 综上所述,针对Oracle数据库表内敏感信息字段的加解密需求,可以通过定义加解密工具类,使用Hibernate或JDBC进行查询和存储时调用加解密工具类,保证程序的安全性和可维护性。同时,封装成jar包或公共模块,提高代码的复用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值