配置文件配置字段
# 敏感字段配置
sensitive:
fields: password,idCard,phone,email,address # 需要加密的敏感字段列表
读取配置文件类
@Component
@ConfigurationProperties(prefix = "sensitive")
@Data
@AllArgsConstructor
public class SensitiveFieldProperties {
private List<String> fields;
public boolean sensitiveField(String name) {
return fields.contains(name);
}
}
自定义注解
/**
* @author qingyuqiao
* @version 1.0
* @description: 操作类型
* @date 2025/7/10 14:27
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Operation {
// 操作类型
OperationType value();
}
定义操作类型
/**
* @author qingyuqiao
* @version 1.0
* @description:
* @date 2025/7/10 14:28
*/
public enum OperationType {
INSERT,
UPDATE,
SELECT,
DELETE,
OTHER
}
AES 加密工具类
/**
* @author qingyuqiao
* @version 1.0
* @description: AES 加密工具类
* @date 2025/7/10 11:04
*/
public class AESUtil {
private static final Logger log = LoggerFactory.getLogger(AESUtil.class);
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
// 密钥必须是16字节(128位)、24字节(192位)或32字节(256位)
private static final String SECRET_KEY = "cilicili-key-16b";
// 获取AES密钥
private static SecretKeySpec getSecretKey() {
byte[] keyBytes = SECRET_KEY.getBytes(StandardCharsets.UTF_8);
// 确保密钥长度为16字节
if (keyBytes.length != 16) {
byte[] fixedKeyBytes = new byte[16];
System.arraycopy(keyBytes, 0, fixedKeyBytes, 0, Math.min(keyBytes.length, 16));
keyBytes = fixedKeyBytes;
}
return new SecretKeySpec(keyBytes, ALGORITHM);
}
/**
* 加密字符串
*
* @param data 要加密的数据
* @return 加密后的Base64编码字符串
*/
public static String encrypt(String data) {
if (data == null || data.isEmpty()) {
return data;
}
try {
SecretKeySpec key = getSecretKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
String result = Base64.getEncoder().encodeToString(encryptedBytes);
log.debug("加密成功: {} -> {}", data, result);
return result;
} catch (Exception e) {
log.error("AES加密失败: {}", e.getMessage(), e);
return data; // 加密失败时返回原始数据
}
}
/**
* 解密字符串
*
* @param encryptedData 加密后的Base64编码字符串
* @return 解密后的原始字符串
*/
public static String decrypt(String encryptedData) {
if (encryptedData == null || encryptedData.isEmpty()) {
return encryptedData;
}
try {
SecretKeySpec key = getSecretKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] decodedBytes;
try {
decodedBytes = Base64.getDecoder().decode(encryptedData);
} catch (IllegalArgumentException e) {
log.error("Base64解码失败,输入不是有效的Base64编码: {}", encryptedData);
return encryptedData;
}
byte[] decryptedBytes = cipher.doFinal(decodedBytes);
String result = new String(decryptedBytes, StandardCharsets.UTF_8);
log.debug("解密成功: {} -> {}", encryptedData, result);
return result;
} catch (Exception e) {
log.error("AES解密失败,可能是密文格式不正确: {}, 错误: {}", encryptedData, e.getMessage());
return encryptedData; // 解密失败时返回原始数据
}
}
/**
* 测试加密和解密是否一致
*
* @param data 测试数据
* @return 测试结果
*/
public static boolean testEncryptDecrypt(String data) {
try {
String encrypted = encrypt(data);
String decrypted = decrypt(encrypted);
boolean success = data.equals(decrypted);
log.info("加解密测试 - 原始数据: {}, 加密结果: {}, 解密结果: {}, 测试结果: {}",
data, encrypted, decrypted, success ? "成功" : "失败");
return success;
} catch (Exception e) {
log.error("加解密测试失败: {}", e.getMessage(), e);
return false;
}
}
public static void main(String[] args) {
testEncryptDecrypt("123456");
}
}
实现加解密操作的切面
/**
* 敏感数据处理切面
* 在保存前加密,查询后解密
*/
@Aspect
@Component
@Slf4j
public class SensitiveDataAspect {
@Autowired
private SensitiveFieldProperties sensitiveFieldProperties;
/**
* 环切注解
*
* @param joinPoint
* @param operation
* @return
*/
@Around("@annotation(operation)")
public Object handleOperation(ProceedingJoinPoint joinPoint, Operation operation) throws Throwable {
// 获取操作类型的值
OperationType type = operation.value();
// 获取参数
Object[] args = joinPoint.getArgs();
// 对新增和修改操作进行加密
if (type == OperationType.INSERT || type == OperationType.UPDATE) {
for (Object arg : args) {
encryptFields(arg);
}
}
// 获取方法执行结果
Object result = joinPoint.proceed();
// 执行后置通知
if (type == OperationType.SELECT && result != null) {
// 处理集合结果
if (result instanceof Collection) {
Collection collection = (Collection) result;
for (Object item : collection) {
decryptFields(item);
}
} else {
// 处理单个结果
decryptFields(result);
}
}
return result;
}
// 解密操作
private void decryptFields(Object obj) {
// 判断是否为空
if (obj == null) return;
for (Field field : obj.getClass().getDeclaredFields()) {
if (!field.getType().equals(String.class)) continue;
if (!sensitiveFieldProperties.sensitiveField(field.getName())) continue;
field.setAccessible(true);
try {
// 获取对应字段的值
String val = (String) field.get(obj);
if (val != null && !val.isEmpty()) {
field.set(obj, AESUtil.decrypt(val));
}
} catch (IllegalAccessException e) {
throw new RuntimeException("字段加密失败", e);
}
}
}
// 加密操作
private void encryptFields(Object obj) {
if (obj == null) return;
// 处理
for (Field field : obj.getClass().getDeclaredFields()) {
// 判断是否是String类型
if (!field.getType().equals(String.class)) continue;
// 是否需要加密
if (!sensitiveFieldProperties.sensitiveField(field.getName())) continue;
// 设置为可访问
field.setAccessible(true);
try {
String val = (String) field.get(obj);
if (val != null && !val.isEmpty()) {
field.set(obj, AESUtil.encrypt(val));
}
} catch (IllegalAccessException e) {
throw new RuntimeException("字段加密失败", e);
}
}
}
}
注意点
打Operation 注解时,SELECT 类型必须打在service 层或是返回结果类型的方法上。
错误示范:
@GetMapping("/get/{id}")
@SaCheckPermission(value = "userInfo:get", orRole = "admin")
@ApiOperation("获取用户信息")
@OperationLog(module = "用户管理", operationType = "查询", description = "获取用户信息")
@Operation(OperationType.SELECT)
public Result<UserInfo> get(@PathVariable("id") String id) {
return Result.ok("获取用户信息成功", userInfoService.get(id));
}
正确示范:
UserInfo 对象的字段进行加密
@Override
@Operation(OperationType.SELECT)
public UserInfo get(String id) {
return baseMapper.selectById(id);
}