文章目录
- 前言
- 一、数据库字段加解密实现
- 1. 定义加密类型枚举
- 2. 定义AES密钥和偏移量
- 3. 配置定义使用的加密类型
- 4. 加密解密接口
- 5. 解密解密异常类
- 6. 加密解密实现类
- 6.1 AES加密解密实现类
- 6.2 Base64加密解密实现类
- 7. 实现数据库的字段保存加密与查询解密处理类
- 8. MybatisPlus配置类
- 二、数据库字段加密解密的使用
- 1. 创建实体类
- 2. 数据保存示例
- 3. 数据查询示例
前言
本文将基于Mybatis-Plus讲述如何在数据的源头存储层保障其安全。我们都知道一些核心私密字段,比如说密码,手机号等在数据库层存储就不能明文存储,必须加密存储保证即使数据库泄露了也不会轻易曝光数据。
本文实现效果参考 plasticene-boot-starter-parent,更多信息在下面链接。
Github地址:https://github.com/plasticene/plasticene-boot-starter-parent
Gitee地址:https://gitee.com/plasticene3/plasticene-boot-starter-parent
当然也可以参考 mybatis-mate 为 mp 企业级模块,旨在更敏捷优雅处理数据。
Gitee地址:https://gitee.com/baomidou/mybatis-mate-examples
一、数据库字段加解密实现
1. 定义加密类型枚举
默认提供基于base64和AES加密算法,当然也可以自定义加密算法。
public enum Algorithm {BASE64,AES
}
2. 定义AES密钥和偏移量
@Data
@ConfigurationProperties(prefix = "ptc.encrypt")
public class EncryptProperties {/*** 加密算法 {@link Algorithm}*/private Algorithm algorithm = Algorithm.BASE64;/*** aes算法需要秘钥key*/private String key = "8iUJAD805IHO2vog";/*** aes算法需要一个偏移量* AES算法的偏移量长度必须为16字节(128位)*/private String iv = "cUTd1U+yxk8Dl6Cg";}
AES的密钥和偏移量的生成可访问:AES 密钥在线生成器
若使用RSA,密钥对的生成可访问:在线生成非对称加密公钥私钥对
3. 配置定义使用的加密类型
这里我们使用aes加密算法:
- application.yml
ptc:encrypt:algorithm: aes
4. 加密解密接口
public interface EncryptService {/*** 加密算法* @param content* @return*/String encrypt(String content);/*** 解密算法* @param content* @return*/String decrypt(String content);}
5. 解密解密异常类
- BizException.java
/*** 业务异常类*/
@Data
public class BizException extends RuntimeException {private Integer code;public BizException() {super();}public BizException(String message) {super(message);}public BizException(Integer code, String message) {super(message);this.code = code;}}
6. 加密解密实现类
6.1 AES加密解密实现类
@Slf4j
public class AESEncryptService implements EncryptService {@Resourceprivate EncryptProperties encryptProperties;@Overridepublic String encrypt(String content) {try {SecretKeySpec secretKey = new SecretKeySpec(encryptProperties.getKey().getBytes(StandardCharsets.UTF_8), Constants.AES);byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, Constants.AES);IvParameterSpec iv = new IvParameterSpec(encryptProperties.getIv().getBytes(StandardCharsets.UTF_8));Cipher cipher = Cipher.getInstance(Constants.AES_CBC_CIPHER);cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, iv);byte[] valueByte = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));return Base64.getEncoder().encodeToString(valueByte);} catch (Exception e) {log.error("加密失败:", e);throw new BizException("加密失败");}}@Overridepublic String decrypt(String content) {try {byte[] originalData = Base64.getDecoder().decode(content.getBytes(StandardCharsets.UTF_8));SecretKeySpec secretKey = new SecretKeySpec(encryptProperties.getKey().getBytes(StandardCharsets.UTF_8), Constants.AES);byte[] enCodeFormat = secretKey.getEncoded();SecretKeySpec secretKeySpec = new SecretKeySpec(enCodeFormat, Constants.AES);IvParameterSpec iv = new IvParameterSpec(encryptProperties.getIv().getBytes(StandardCharsets.UTF_8));Cipher cipher = Cipher.getInstance(Constants.AES_CBC_CIPHER);cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, iv);byte[] valueByte = cipher.doFinal(originalData);return new String(valueByte);} catch (Exception e) {log.error("解密失败:", e);throw new BizException("解密失败");}}
}
6.2 Base64加密解密实现类
public class Base64EncryptService implements EncryptService {@Overridepublic String encrypt(String content) {try {return Base64.getEncoder().encodeToString(content.getBytes(StandardCharsets.UTF_8));} catch (Exception e) {throw new RuntimeException("encrypt fail!", e);}}@Overridepublic String decrypt(String content) {try {byte[] asBytes = Base64.getDecoder().decode(content);return new String(asBytes, StandardCharsets.UTF_8);} catch (Exception e) {throw new RuntimeException("decrypt fail!", e);}}
}
7. 实现数据库的字段保存加密与查询解密处理类
接下来就可以基于加密算法,扩展 MyBatis 的 BaseTypeHandler 对实体字段数据进行加密解密
public class EncryptTypeHandler<T> extends BaseTypeHandler<T> {@Resourceprivate EncryptService encryptService;@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {ps.setString(i, encryptService.encrypt((String)parameter));}@Overridepublic T getNullableResult(ResultSet rs, String columnName) throws SQLException {String columnValue = rs.getString(columnName);//有一些可能是空字符return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);}@Overridepublic T getNullableResult(ResultSet rs, int columnIndex) throws SQLException {String columnValue = rs.getString(columnIndex);return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);}@Overridepublic T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {String columnValue = cs.getString(columnIndex);return StrUtil.isBlank(columnValue) ? (T)columnValue : (T)encryptService.decrypt(columnValue);}
}
8. MybatisPlus配置类
把EncryptService实现类注入到容器中
@Configuration
@MapperScan("com.chh.mapper")
@EnableConfigurationProperties({EncryptProperties.class})
public class MybatisPlusConfig {@Resourceprivate EncryptProperties encryptProperties;// 分页插件@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL));return interceptor;}@Beanpublic EncryptTypeHandler encryptTypeHandler() {return new EncryptTypeHandler();}@Bean@ConditionalOnMissingBean(EncryptService.class)public EncryptService encryptService() {Algorithm algorithm = encryptProperties.getAlgorithm();EncryptService encryptService;switch (algorithm) {case BASE64:encryptService = new Base64EncryptService();break;case AES:encryptService = new AESEncryptService();break;default:encryptService = null;}return encryptService;}}
二、数据库字段加密解密的使用
1. 创建实体类
- 在实体类上加上
@TableName(value = "表名", autoResultMap = true)
- 在需要加密的属性上加上
@TableField(value = "字段", typeHandler = EncryptTypeHandler.class)
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName(value = "app_account_login", autoResultMap = true)
public class AppAccountLogin implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Integer id;/*** 名称*/@TableField("name")private String name;/*** 登录账号*/@TableField("login_account")private String loginAccount;/*** 登录密码*/@TableField(value = "login_password", typeHandler = EncryptTypeHandler.class)private String loginPassword;/*** 激活状态*/@TableField("enabled")private Boolean enabled;/*** 创建时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField(fill = FieldFill.INSERT)private Date createTime;}
2. 数据保存示例
AppAccountLogin appAccountLogin = new AppAccountLogin();
appAccountLogin.setName("test");
appAccountLogin.setLoginAccount("123456789");
appAccountLogin.setLoginPassword("abc123456");
appAccountLoginService.save(appAccountLogin);
保存结果:
3. 数据查询示例
System.out.println(appAccountLoginService.getById(4));
查询结果:
AppAccountLogin(id=4, name=test, loginAccount=123456789, loginPassword=abc123456, enabled=true, createTime=Fri Feb 02 10:02:41 CST 2024)