文章目錄
- 前言
- 一、數據庫字段加解密實現
- 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)