SpringBoot实现全局参数加密解密

提供给C端使用的程序,特别是银行、电商类的项目,其实都挺害怕参数被抓包,进而进行接口攻击或者推测出表结构、数据结构等。所以一个全局化的参数加密解密功能还是很有必要的。

一、整体架构设计

本方案采用​​RSA+AES混合加密体系​​,结合Spring的RequestBodyAdviceResponseBodyAdvice机制,实现请求/响应全流程的透明化加解密:

  1. ​客户端生成​​:每次请求随机生成AES密钥和IV(初始化向量)
  2. ​RSA加密​​:用服务端公钥加密AES密钥和IV
  3. ​请求传输​​:密文参数 + 加密后的AES密钥(HTTP Header)
  4. ​服务端解密​​:用RSA私钥解密获得AES密钥 → 解密请求体
  5. ​动态开关​​:通过Apollo配置中心控制全局加解密开关

核心处理流程

二、核心实现

1. 定义配置类

@Configuration
public class EncryptionConfig {
    @Bean
    @ConfigurationProperties(prefix = "encryption")
    public EncryptionProperties encryptionProperties() {
        return new EncryptionProperties();
    }
}

@Data
public class EncryptionProperties {
    private boolean enabled;
    private List<String> excludedPaths = new ArrayList<>();
}

2. 实现注解控制

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SkipDecrypt {
    // true表示跳过解密,false表示需要解密
    boolean value() default true;
}

3. 加解密工具类

public class CryptoUtils {
    
    // RSA解密(获取AES密钥)
    public static byte[] rsaDecrypt(byte[] encryptedData, PrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            return cipher.doFinal(encryptedData);
        } catch (Exception e) {
            throw new CryptoException("RSA解密失败", e);
        }
    }
    
    // AES解密请求体
    public static String aesDecrypt(String encryptedData, byte[] aesKey, byte[] iv) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            
            byte[] decoded = Base64.getDecoder().decode(encryptedData);
            return new String(cipher.doFinal(decoded), StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new CryptoException("AES解密失败", e);
        }
    }
}

4. 实现RequestBodyAdvice解密控制

@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Autowired
    private EncryptionProperties properties;
    
    @Autowired
    private PrivateKey rsaPrivateKey; // 注入预加载的RSA私钥

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, 
                            Class<? extends HttpMessageConverter<?>> converterType) {
        // 1. 检查Apollo全局开关
        if (!properties.isEnabled()) return false;
        
        HttpServletRequest request = 
            ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String path = request.getRequestURI();
        
        // 2. 检查排除路径
        if (properties.getExcludedPaths().contains(path)) {
            return false;
        }
        
        // 3. 检查方法注解(默认需要解密,只有显式设置false才跳过)
        SkipDecrypt annotation = methodParameter.getMethodAnnotation(SkipDecrypt.class);
        return annotation == null || !annotation.value();
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,
                                          Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        
        HttpServletRequest request = 
            ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        
        // 获取Header中的加密密钥
        String encryptedAesKey = request.getHeader("encrypted-aes-key");
        String encryptedIv = request.getHeader("encrypted-iv");
        
        // RSA解密获得AES密钥和IV
        byte[] aesKey = CryptoUtils.rsaDecrypt(Base64.getDecoder().decode(encryptedAesKey), rsaPrivateKey);
        byte[] iv = CryptoUtils.rsaDecrypt(Base64.getDecoder().decode(encryptedIv), rsaPrivateKey);
        
        try {
            // 读取并解密请求体
            String encryptedBody = StreamUtils.copyToString(inputMessage.getBody(), StandardCharsets.UTF_8);
            String decryptedBody = CryptoUtils.aesDecrypt(encryptedBody, aesKey, iv);
            
            // 构建解密后的输入流
            return new DecryptedHttpInputMessage(
                new ByteArrayInputStream(decryptedBody.getBytes()),
                inputMessage.getHeaders()
            );
        } catch (IOException e) {
            throw new IllegalStateException("请求体读取失败", e);
        }
    }
    
    // 其他必要方法实现...
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, ...) { return body; }
    @Override
    public Object handleEmptyBody(Object body, ...) { return body; }
}

三、动态开关与路径排除策略

1. Apollo监听配置变更

@Component
public class EncryptionConfigRefresher {
    @Autowired
    private EncryptionProperties properties;
    
    @ApolloConfigChangeListener
    public void onChange(ConfigChangeEvent changeEvent) {
        if (changeEvent.isChanged("encryption.enabled")) {
            boolean newValue = Boolean.parseBoolean(
                changeEvent.getChange("encryption.enabled").getNewValue());
            properties.setEnabled(newValue);
        }
        
        if (changeEvent.isChanged("encryption.excludedPaths")) {
            String paths = changeEvent.getChange("encryption.excludedPaths").getNewValue();
            properties.setExcludedPaths(Arrays.asList(paths.split(",")));
        }
    }
}

2. 路径匹配逻辑优化

// 在supports方法中实现智能路径匹配
private boolean isExcludedPath(String requestPath) {
    return properties.getExcludedPaths().stream()
        .anyMatch(excludedPath -> {
            // 支持Ant风格路径匹配
            PathMatcher matcher = new AntPathMatcher();
            return matcher.match(excludedPath, requestPath);
        });
}

四、总结

本文实现的全局加解密方案具有以下优势:

  1. ​零侵入性​​:业务代码无需感知加解密过程
  2. ​动态控制​​:通过Apollo实现运行时配置热更新
  3. ​灵活策略​​:支持全局开关、路径排除和方法级注解控制
  4. ​安全传输​​:采用RSA+AES混合加密体系,确保密钥安全交换

通过RequestBodyAdvice/ResponseBodyAdvice机制,我们成功构建了可插拔的加解密架构。该方案已在多个金融级项目中验证,可支撑日均百万级API调用的安全需求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值