Java 使用mybatis的BaseTypeHandler实现数据自动AES加密解密,通过Hutool工具类自定义注解实现数据脱【附有完整步骤和代码】
2023-12-15 18:04:51
一、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
@NoArgsConstructor
public 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;
文章来源:https://blog.csdn.net/weixin_44934104/article/details/135019932
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!