利用注解+RequestBodyAdvice实现http请求内容加解密

博客介绍了指定controller方法加解密的注解使用,添加注解到controller方法上。对于加密请求数据,需在二进制流处理前用RequestBodyAdvice的beforeBodyRead方法解密,通过AppID查秘钥解密后替换HttpInputMessage。加密使用ResponseBodyAdvice,之前博文有介绍。
ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

注解主要用来指定那些需要加解密的controller方法,实现比较简单


@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretAnnotation {
    boolean encode() default false;
    boolean decode() default false;

}

使用时添加注解在controller的方法上

    @PostMapping("/preview")
    @SecretAnnotation(decode = true)
    public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO)  {
        return contractSignService.previewContract(fillContractDTO);
    }

请求数据由二进制流转为类对象数据,对于加密过的数据,需要在二进制流被处理之前进行解密,否则在转为类对象时会因为数据格式不匹配而报错。
因此使用RequestBodyAdvice的beforeBodyRead方法来处理。

@Slf4j
@RestControllerAdvice
public class MyRequestControllerAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasParameterAnnotation(RequestBody.class);
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Autowired
    private MySecretUtil mySecretUtil;
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {
            SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);
            if (secretAnnotation.decode()) {
                return new HttpInputMessage() {
                    @Override
                    public InputStream getBody() throws IOException {
                        List<String> appIdList = httpInputMessage.getHeaders().get("appId");
                        if (appIdList.isEmpty()){
                            throw new RuntimeException("请求头缺少appID");
                        }
                        String appId = appIdList.get(0);
                        String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");

                        bodyStr = mySecretUtil.decode(bodyStr,appId);
                        return  IOUtils.toInputStream(bodyStr,"utf-8");
                    }

                    @Override
                    public HttpHeaders getHeaders() {
                        return httpInputMessage.getHeaders();
                    }
                };
            }
        }
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {


        return o;
    }

}

mySecretUtil.decode(bodyStr,appId)的内容是,通过请求头中的AppID去数据库中查找对于的秘钥,之后进行解密,返回解密后的字符串。再通过common.io包中提供的工具类IOUtils将字符串转为inputstream流,替换HttpInputMessage,返回一个body数据为解密后的二进制流的HttpInputMessage。

加密使用ResponseBodyAdvice,在我之前的一篇博文里有介绍过。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

### 如何使用AOP实现HTTP请求参数Body的解密功能 #### 背景介绍 面向切面编程(Aspect-Oriented Programming,简称AOP)是一种设计模式,用于分离横切关注点(如日志记录、事务管理、安全控制等)。在Web应用开发中,可以通过AOP来处理请求参数的安全性问题,比如对请求体中的敏感数据进行加密解密操作。 为了实现HTTP请求参数Body的解密功能,可以利用Spring框架提供的`RequestBodyAdvice`机制。该机制允许开发者拦截并修改控制器方法接收到的请求内容[^1]。结合AOP技术,可以在不影响业务逻辑的前提下完成解密工作。 --- #### 实现方案概述 以下是基于`RequestBodyAdvice`与AOP相结合的方式实现请求参数Body解密的核心思路: 1. **创建自定义注解** 定义一个注解标记哪些接口需要启用解密功能。例如: ```java @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface Decrypt { String[] params() default {}; // 需要解密的字段名称数组 } ``` 2. **编写解密工具类** 提供通用的解密算法实现。假设我们采用AES作为加密标准,则可封装如下工具函数: ```java import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AESUtil { private static final String ALGORITHM = "AES"; public static String decrypt(String encryptedData, String key) throws Exception { Cipher cipher = Cipher.getInstance(ALGORITHM); SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); byte[] decodedBytes = Base64.getDecoder().decode(encryptedData); return new String(cipher.doFinal(decodedBytes)); } } ``` 3. **扩展RequestBodyAdviceAdapter抽象类** 创建具体的`RequestBodyAdvice`实现类,重写其中的关键方法以支持动态调整输入流的行为。 ```java import org.springframework.core.MethodParameter; import org.springframework.http.HttpInputMessage; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.reflect.Type; import java.nio.charset.StandardCharsets; @ControllerAdvice public class CustomRequestBodyAdvice extends RequestBodyAdviceAdapter { @Override public boolean supports(MethodParameter methodParam, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParam.hasMethodAnnotation(Decrypt.class); // 判断是否有@Decrypt标注 } @Override public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<?> converterType) throws IOException { if (!supports(parameter, targetType, converterType)) { return inputMessage; } try { String requestBody = new String(inputMessage.getBody().readAllBytes(), StandardCharsets.UTF_8); String decryptedContent = processDecryption(requestBody, parameter.getMethodAnnotation(Decrypt.class).params()); return new ByteArrayInputStream(decryptedContent.getBytes(StandardCharsets.UTF_8)); } catch (Exception ex) { throw new RuntimeException("Failed to decrypt request body.", ex); } } private String processDecryption(String content, String[] paramNames) throws Exception { StringBuilder resultBuilder = new StringBuilder(content); for (String paramName : paramNames) { int startIdx = content.indexOf("\"" + paramName + "\":\""); // 查找目标字段位置 if (startIdx != -1) { int endQuotePos = content.indexOf('"', startIdx + paramName.length() + 4); String encryptedValue = content.substring(startIdx + paramName.length() + 4, endQuotePos); String decryptedText = AESUtil.decrypt(encryptedValue, "your-secret-key"); resultBuilder.replace(startIdx + paramName.length() + 4, endQuotePos, decryptedText); } } return resultBuilder.toString(); } } ``` 4. **配置AOP切入点** 如果希望进一步增强灵活性,还可以引入Spring AOP模块定义额外的环绕通知逻辑。例如: ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class SecurityAspect { @Around("@annotation(com.example.Decrypt)") public Object handleRequestWithEncryption(ProceedingJoinPoint joinPoint) throws Throwable { long startTime = System.currentTimeMillis(); try { return joinPoint.proceed(); // 执行原生方法调用链路 } finally { System.out.println("Execution time: " + (System.currentTimeMillis() - startTime) + "ms."); } } } ``` 5. **测试验证** 假设存在这样一个RESTful API端点接受JSON格式的数据包: ```json { "username":"admin", "password":"cGFzc3dvcmQ=" /* base64编码后的明文密码 */ } ``` 当客户端发送上述POST请求至服务器时,后台会自动解析出隐藏于`password`属性内的真实值,并将其替换为原始形式后再交给实际处理器继续运作。 --- ### 注意事项 尽管此方法能够有效简化跨层间的重复劳动量,但也存在一定局限性需要注意: - 性能开销较大:每次都需要重新序列化整个对象树结构; - 可维护性较差:一旦涉及多种不同的加解密协议则需频繁改动现有代码基线[^2]。 因此建议仅针对特定场景谨慎选用此类设计方案。 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值