当前位置:首页 » 《随便一记》 » 正文

Java 使用mybatis的BaseTypeHandler实现数据自动AES加密解密,通过Hutool工具类自定义注解实现数据脱【附有完整步骤和代码】

3 人参与  2024年02月12日 18:26  分类 : 《随便一记》  评论

点击全文阅读


一、AES加密

1 加密工具类

使用KeyGenerator生成AES算法生成器

public class AESUtil {    /**     * 密钥长度: 128, 192 or 256     */    private static final int KEY_SIZE = 256;    /**     * 加密/解密算法名称     */    private static final String ALGORITHM = "AES";    /**     * 随机数生成器(RNG)算法名称     */    private static final String RNG_ALGORITHM = "SHA1PRNG";    /**     * 生成密钥的种子不可泄露  16位     */    public static final String KEY = "xxxxxxxxxxxxxxxx";    /**     * 生成密钥对象     */    private static SecretKey generateKey(byte[] key) throws Exception {        // 创建安全随机数生成器        SecureRandom random = SecureRandom.getInstance(RNG_ALGORITHM);        // 设置 密钥key的字节数组 作为安全随机数生成器的种子        random.setSeed(key);        // 创建 AES算法生成器        KeyGenerator gen = KeyGenerator.getInstance(ALGORITHM);        // 初始化算法生成器        gen.init(KEY_SIZE, random);        // 生成 AES密钥对象, 也可以直接创建密钥对象: return new SecretKeySpec(key, ALGORITHM);        return gen.generateKey();    }    /**     * 数据加密: 明文 -> 密文     */    public static String encrypt(String content, byte[] key) throws Exception {        // 生成密钥对象        SecretKey secKey;        try {            secKey = generateKey(key);            // 获取 AES 密码器            Cipher cipher = Cipher.getInstance(ALGORITHM);            // 初始化密码器(加密模型)            cipher.init(Cipher.ENCRYPT_MODE, secKey);            // 加密数据, 返回密文            byte[] result = new byte[]{};            if(!StringUtils.isEmpty(content)){                byte[]  plainBytes = content.getBytes("utf-8");                result = cipher.doFinal(plainBytes);            }            return Base64.getEncoder().encodeToString(result);//通过Base64转码返回        } catch (Exception e) {            throw new Exception( "AES 加密失败:" + e.getMessage());        }    }    /**     * 数据解密: 密文 -> 明文     */    public static String decrypt(String content, byte[] key) throws Exception {        try {            // 生成密钥对象            SecretKey secKey = generateKey(key);            // 获取 AES 密码器            Cipher cipher = Cipher.getInstance(ALGORITHM);            // 初始化密码器(解密模型)            cipher.init(Cipher.DECRYPT_MODE, secKey);            // 解密数据, 返回明文            byte[] result = new byte[]{};            if(!StringUtils.isEmpty(content)){                result = cipher.doFinal(Base64.getDecoder().decode(content));            }            return new String(result, "utf-8");        } catch (Exception e) {            throw new Exception("AES 解密失败:" + e.getMessage());        }    }    /**     * 加密文件: 明文输入 -> 密文输出     */    public static void encryptFile(File plainIn, File cipherOut, byte[] key) throws Exception {        aesFile(plainIn, cipherOut, key, true);    }    /**     * 解密文件: 密文输入 -> 明文输出     */    public static void decryptFile(File cipherIn, File plainOut, byte[] key) throws Exception {        aesFile(plainOut, cipherIn, key, false);    }    /**     * AES 加密/解密文件     */    private static void aesFile(File plainFile, File cipherFile, byte[] key, boolean isEncrypt) throws Exception {        // 获取 AES 密码器        Cipher cipher = Cipher.getInstance(ALGORITHM);        // 生成密钥对象        SecretKey secKey = generateKey(key);        // 初始化密码器        cipher.init(isEncrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, secKey);        // 加密/解密数据        InputStream in = null;        OutputStream out = null;        try {            if (isEncrypt) {                // 加密: 明文文件为输入, 密文文件为输出                in = new FileInputStream(plainFile);                out = new FileOutputStream(cipherFile);            } else {                // 解密: 密文文件为输入, 明文文件为输出                in = new FileInputStream(cipherFile);                out = new FileOutputStream(plainFile);            }            byte[] buf = new byte[1024];            int len = -1;            // 循环读取数据 加密/解密            while ((len = in.read(buf)) != -1) {                out.write(cipher.update(buf, 0, len));            }            out.write(cipher.doFinal());    // 最后需要收尾            out.flush();        } finally {            close(in);            close(out);        }    }    private static void close(Closeable c) {        if (c != null) {            try {                c.close();            } catch (IOException e) {                // nothing            }        }    }}

2、TypeHandler类型处理器

使用Mybatis中的TypeHandler类型处理器,定义一个实现自动加密解密的处理器

BaseTypeHandler背景
BaseTypeHandler是Mybatis中的一个基类,他的作用有如下几点:
类型处理器的基类Mybatis中的TypeHandler类型处理器,用于JavaType和jdbcType转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值。MyBatis 内置了很多TypeHandler可以实现BaseTypeHandler,自定义 TypeHandler

@MappedJdbcTypes(JdbcType.VARCHAR)public class AESEncryptHandler extends BaseTypeHandler<Object> {    @Override    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {        String content = (parameter == null) ? "" : parameter.toString();        try {            ps.setString(i, AESUtil.encrypt(content, AESUtil.KEY.getBytes()));        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {        String columnValue = rs.getString(columnName);        try {            return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public Object getNullableResult(ResultSet rs, int columnIndex) throws SQLException {        String columnValue = rs.getString(columnIndex);        try {            return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());        } catch (Exception e) {            throw new RuntimeException(e);        }    }    @Override    public Object getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {        String columnValue = cs.getString(columnIndex);        try {            return AESUtil.decrypt(columnValue,  AESUtil.KEY.getBytes());        } catch (Exception e) {            throw new RuntimeException(e);        }    }}

3、在实体Bean的字段上使用注解

    /**     * 手机号码 AES加密     */    @ApiModelProperty(value = "手机号码")    @TableField(typeHandler = AESEncryptHandler.class)    private java.lang.String phone;

4、自定义SQL中使用resultMap

此时在mybatis plus中是可以正常使用的,但是当我们在xml中自定义SQL文件时无效,这时需要在xml中定义resultMap

(1) 在实体中设置:autoResultMap = true

@Data@NoArgsConstructor@AllArgsConstructor@TableName(value = "sys_user",autoResultMap = true)public class SysUser extends BaseBean implements Serializable {}

(2)在xml中设置返回数据类型

<resultMap id="objectMap" type="com.depu.ems.bean.system.SysUser">        <id column="id" property="id" />        <result property="phone" column="phone" typeHandler="com.depu.ems.common.util.AESEncryptHandler"/>    </resultMap> <select id="getObjectList"  resultMap="objectMap">SELECT *     FROM table_name </select>

需要手机加密解密的使用场景:
1)xml中自定义sql 返回实体类不能解密,返回resultMap在map中配置typeHandler可以解密。
2)自带的wrappers更新不能加密,需要将数据加密后更新。

二、数据脱敏

1、引入hutool

<dependency>    <groupId>cn.hutool</groupId>    <artifactId>hutool-all</artifactId>    <version>5.8.10</version></dependency>

2、配合JackSon通过注解方式实现脱敏

(1)定义脱敏的枚举类

public enum DesensitizationTypeEnum {    //自定义    MY_RULE,    //用户id    USER_ID,    //中文名    CHINESE_NAME,    //身份证号    ID_CARD,    //座机号    FIXED_PHONE,    //手机号    MOBILE_PHONE,    //地址    ADDRESS,    //电子邮件    EMAIL,    //密码    PASSWORD,    //中国大陆车牌,包含普通车辆、新能源车辆    CAR_LICENSE,    //银行卡    BANK_CARD}

(2)定义一个用于脱敏的 Desensitization 注解

/** * @Retention(RetentionPolicy.RUNTIME):运行时生效。 * @Target(ElementType.FIELD):可用在字段上。 * @JacksonAnnotationsInside:此注解可以点进去看一下是一个元注解,主要是用户打包其他注解一起使用。 * @JsonSerialize:上面说到过,该注解的作用就是可自定义序列化,可以用在注解上,方法上,字段上,类上,运行时生效等等,根据提供的序列化类里面的重写方法实现自定义序列化。 */@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@JacksonAnnotationsInside@JsonSerialize(using = DesensitizationSerialize.class)public @interface Desensitization {    /**     * 脱敏数据类型,在MY_RULE的时候,startInclude和endExclude生效     */    DesensitizationTypeEnum type() default DesensitizationTypeEnum.MY_RULE;    /**     * 脱敏开始位置(包含)     */    int startInclude() default 0;    /**     * 脱敏结束位置(不包含)     */    int endExclude() default 0;}

(3)创建自定的序列化类

import cn.hutool.core.text.CharSequenceUtil;import cn.hutool.core.util.DesensitizedUtil;import com.fasterxml.jackson.core.JsonGenerator;import com.fasterxml.jackson.databind.BeanProperty;import com.fasterxml.jackson.databind.JsonMappingException;import com.fasterxml.jackson.databind.JsonSerializer;import com.fasterxml.jackson.databind.SerializerProvider;import com.fasterxml.jackson.databind.ser.ContextualSerializer;import lombok.AllArgsConstructor;import lombok.NoArgsConstructor;import org.jeecg.common.constant.enums.DesensitizationTypeEnum;import org.jeecg.common.system.annotation.Desensitization;import java.io.IOException;import java.util.Objects;/** * @description: 自定义序列化类 * @author: Zhangxue * @time: 2023/12/15 15:57 */@AllArgsConstructor@NoArgsConstructorpublic class DesensitizationSerialize extends JsonSerializer<String> implements ContextualSerializer {    private DesensitizationTypeEnum type;    private Integer startInclude;    private Integer endExclude;    @Override    public void serialize(String str, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {        switch (type) {            // 自定义类型脱敏            case MY_RULE:                jsonGenerator.writeString(CharSequenceUtil.hide(str, startInclude, endExclude));                break;            // userId脱敏            case USER_ID:                jsonGenerator.writeString(String.valueOf(DesensitizedUtil.userId()));                break;            // 中文姓名脱敏            case CHINESE_NAME:                jsonGenerator.writeString(DesensitizedUtil.chineseName(String.valueOf(str)));                break;            // 身份证脱敏            case ID_CARD:                jsonGenerator.writeString(DesensitizedUtil.idCardNum(String.valueOf(str), 1, 2));                break;            // 固定电话脱敏            case FIXED_PHONE:                jsonGenerator.writeString(DesensitizedUtil.fixedPhone(String.valueOf(str)));                break;            // 手机号脱敏            case MOBILE_PHONE:                jsonGenerator.writeString(DesensitizedUtil.mobilePhone(String.valueOf(str)));                break;            // 地址脱敏            case ADDRESS:                jsonGenerator.writeString(DesensitizedUtil.address(String.valueOf(str), 8));                break;            // 邮箱脱敏            case EMAIL:                jsonGenerator.writeString(DesensitizedUtil.email(String.valueOf(str)));                break;            // 密码脱敏            case PASSWORD:                jsonGenerator.writeString(DesensitizedUtil.password(String.valueOf(str)));                break;            // 中国车牌脱敏            case CAR_LICENSE:                jsonGenerator.writeString(DesensitizedUtil.carLicense(String.valueOf(str)));                break;            // 银行卡脱敏            case BANK_CARD:                jsonGenerator.writeString(DesensitizedUtil.bankCard(String.valueOf(str)));                break;            default:        }    }    @Override    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) throws JsonMappingException {        if (beanProperty != null) {            // 判断数据类型是否为String类型            if (Objects.equals(beanProperty.getType().getRawClass(), String.class)) {                // 获取定义的注解                Desensitization desensitization = beanProperty.getAnnotation(Desensitization.class);                // 为null                if (desensitization == null) {                    desensitization = beanProperty.getContextAnnotation(Desensitization.class);                }                // 不为null                if (desensitization != null) {                    // 创建定义的序列化类的实例并且返回,入参为注解定义的type,开始位置,结束位置。                    return new DesensitizationSerialize(desensitization.type(), desensitization.startInclude(),                            desensitization.endExclude());                }            }            return serializerProvider.findValueSerializer(beanProperty.getType(), beanProperty);        }        return serializerProvider.findNullValueSerializer(null);    }}

3、使用

使用@Desensitization注解实现脱敏

    /**     * 手机号码 AES加密、脱敏     */    @ApiModelProperty(value = "手机号码")    @TableField(typeHandler = AESEncryptHandler.class)    @Desensitization(type = DesensitizationTypeEnum.MOBILE_PHONE)    private java.lang.String phone;

三、测试运行结果

1、增
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述
数据库中结果:
在这里插入图片描述

2、查
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述

3、改
测试程序:
在这里插入图片描述
调用:
在这里插入图片描述
数据库结果:
在这里插入图片描述

再次查询:
在这里插入图片描述


点击全文阅读


本文链接:http://zhangshiyu.com/post/68291.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1