基于AOP 实现的敏感字段加解密

配置文件配置字段

# 敏感字段配置
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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Object-v

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值