SpringBoot使用ResponseBodyAdvice和RequestBodyAdvice实现请求体解密、响应体加密

news/2024/12/23 1:20:52/

文章目录

  • 一、写在前面
  • 二、实现细节
    • 1、定义加解密注解
    • 2、请求体解密逻辑
    • 3、响应体加密逻辑
    • 4、测试类
    • 5、测试结果
  • 三、源码分析
    • 1、RequestResponseBodyMethodProcessor
    • 2、RequestBodyAdvice
    • 3、ResponseBodyAdvice

一、写在前面

项目中经常需要对接第三方平台,每次对接都需要对接收的参数进行加密、响应参数进行解密,所以通过SpringMVC的扩展点,实现一个统一的方法,对请求体进行加解密。

二、实现细节

1、定义加解密注解

java">import java.lang.annotation.*;/*** @description: : 请求参数解密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestDecryption {DecryptionType type() default DecryptionType.Type0;
}
java">import java.lang.annotation.*;/*** @description: 响应参数加密*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseEncryption {DecryptionType type() default DecryptionType.Type0;
}
java">/*** 加密类型* 根据业务类型进行扩展*/
public enum DecryptionType {Type0("不加密"),Type1("业务1"),Type2("业务2"),Type3("业务3"),Type4("业务4"),;private String name;DecryptionType(String name) {this.name = name;}public String getName() {return name;}
}

2、请求体解密逻辑

java">import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;/*** @description: 请求参数解密,针对post请求*/@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {/*** 方法上有DecryptionAnnotation注解的,进入此拦截器* 只处理post请求** @param methodParameter 方法参数对象* @param targetType      参数的类型* @param converterType   消息转换器* @return true,进入,false,跳过*/@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(RequestDecryption.class) && methodParameter.hasMethodAnnotation(PostMapping.class);}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {RequestDecryption requestDecryption = parameter.getMethodAnnotation(RequestDecryption.class);if (requestDecryption == null) {return inputMessage;}if (requestDecryption.type() == DecryptionType.Type1) {// 解密逻辑  这里可以做成可扩展的byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody());// TODO 解密逻辑Map<String, String> map = new HashMap<>();JSONObject jsonObject = JSON.parseObject(new String(body));// 进行解密map.put("id", jsonObject.get("entryId") + "99999");map.put("name", jsonObject.get("entryName") + "99999");final ByteArrayInputStream bais = new ByteArrayInputStream(JSON.toJSONBytes(map)); // 再将字节转为输入流return new HttpInputMessage() {@Overridepublic InputStream getBody() throws IOException {return bais;  // 再将输入流返回}@Overridepublic HttpHeaders getHeaders() {return inputMessage.getHeaders();}};}return inputMessage;}/*** 转换之后,执行此方法,解密,赋值** @param body          spring解析完的参数* @param inputMessage  输入参数* @param parameter     参数对象* @param targetType    参数类型* @param converterType 消息转换类型* @return 真实的参数*/@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {System.out.println("解密后的请求报文:" + body);return body;}/*** 如果body为空,转为空对象** @param body          spring解析完的参数* @param inputMessage  输入参数* @param parameter     参数对象* @param targetType    参数类型* @param converterType 消息转换类型* @return 真实的参数*/@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}}

3、响应体加密逻辑

java">import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<Object> {/*** 响应匹配* @param returnType* @param converterType*/@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return returnType.hasMethodAnnotation(ResponseEncryption.class);}// 这里可以重写响应体的内容@Overridepublic Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {RequestDecryption requestDecryption = returnType.getMethodAnnotation(RequestDecryption.class);if (requestDecryption == null) {return body;}if (requestDecryption.type() == DecryptionType.Type1) {// 加密逻辑User user = (User) body;UserEntry userEntry = new UserEntry();userEntry.setEntryName("加密后的名字");userEntry.setEntryId("加密后的id");return userEntry;}return body;}
}

4、测试类

java">/*** 加密后传输的内容*/
public class UserEntry {private String entryId;private String entryName;public String getEntryId() {return entryId;}public void setEntryId(String entryId) {this.entryId = entryId;}public String getEntryName() {return entryName;}public void setEntryName(String entryName) {this.entryName = entryName;}@Overridepublic String toString() {return "UserEntry{" +"entryId='" + entryId + '\'' +", entryName='" + entryName + '\'' +'}';}
}
java">import java.io.Serial;/*** 解密后传输的内容*/
public class User implements java.io.Serializable{@Serialprivate static final long serialVersionUID = -7369845805375954031L;private String id;private String name;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "User{" +"id='" + id + '\'' +", name='" + name + '\'' +'}';}
}
java">import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/test")
public class TestController {@RequestDecryption(type = DecryptionType.Type1)@ResponseEncryption(type = DecryptionType.Type1)@PostMapping("/test1")public User test1(@RequestBody User user) {System.out.println(user);return user;}
}

5、测试结果

我们可以实现对请求的内容进行转换、对响应的内容也进行了转换。
在这里插入图片描述

三、源码分析

1、RequestResponseBodyMethodProcessor

RequestResponseBodyMethodProcessor是一个请求参数的处理器,只处理@RequestBody标注的参数,所以只能用于Post请求。
在这里插入图片描述
在处理的过程中,会获取所有的RequestBodyAdvice,调用其中的方法进行额外的处理:
在这里插入图片描述
在这里插入图片描述

2、RequestBodyAdvice

RequestBodyAdvice是一个接口,提供了三个方法:
在这里插入图片描述
在这里插入图片描述

3、ResponseBodyAdvice

在这里插入图片描述


http://www.ppmy.cn/news/1444721.html

相关文章

爬虫自动调用shell通过脚本运行scrapy爬虫(crawler API)

一、爬虫时如何同时调用shell 1)终端cd项目>>scrapy crawl example 2)打开example.py import scrapy from scrapy.shell import inspect_response#引入shellclass ExampleSpider(scrapy.Spider):name "example"allowed_domains ["example.com"]…

Dokcer容器分布式搭建LNMP+wordpress论坛

目录 引言 一、架构环境 二、搭建容器 &#xff08;一&#xff09;自定义网络 &#xff08;二&#xff09;搭建nginx容器 1.文件准备 2.查看与编辑文件 3.生成镜像 4.创建容器 &#xff08;三&#xff09;搭建MySQL容器 1.文件准备 2.查看与编辑文件 3.生成镜像 …

变革 Perplexica:AI驱动的问答搜索引擎

Perplexica是一个开源的人工智能搜索工具&#xff0c;也可以说是一款人工智能搜索引擎&#xff0c;它深入互联网以找到答案。受Perplexity AI启发&#xff0c;它是一个开源选择&#xff0c;不仅可以搜索网络&#xff0c;还能理解您的问题。它使用先进的机器学习算法&#xff0c…

firefox 浏览器常见问题(技巧)总结

目录 问题火狐浏览器firefox 如何取消更新提醒? 待续、更新中 问题 火狐浏览器firefox 如何取消更新提醒? 1、用户在电脑桌面上找到火狐浏览器&#xff0c;接着用鼠标右键点击&#xff0c;在弹出来的右键菜单中&#xff0c;用户选择其中的打开文件所在的位置选项火狐浏览器怎…

探索UTONMOS《神念无界-源起山海》元宇宙游戏的奇幻世界

在科技的前沿&#xff0c;元宇宙游戏如同一扇神秘的大门&#xff0c;缓缓开启&#xff0c;引领我们进入一个前所未有的奇幻世界。 UTONMOS《神念无界-源起山海》元宇宙游戏是数字世界的巅峰之作&#xff0c;它打破了现实与虚拟的界限&#xff0c;让玩家能够身临其境地体验各种奇…

踏上R语言之旅:解锁数据世界的神秘密码(四)

文章目录 前言一、多元线性回归1.多元线性回归模型的建立2.多元线性回归模型的检验 二、多元线性相关分析1.矩阵相关分析2.复相关分析 三、回归变量的选择方法1.变量选择准则2.变量选择的常用准则3.逐步回归分析 总结 前言 回归分析研究的主要对象是客观事物变量间的统计关系。…

JavaEE技术之MySql高级(索引、索引优化、sql实战、View视图、Mysql日志和锁、多版本并发控制)

文章目录 1. MySQL简介2. MySQL安装2.1 MySQL8新特性2.2 安装MySQL2.2.1 在docker中创建并启动MySQL容器&#xff1a;2.2.2 修改mysql密码2.2.3 重启mysql容器2.2.4 常见问题解决 2.3 字符集问题2.4 远程访问MySQL(用户与权限管理)2.4.0 远程连接问题1、防火墙2、账号不支持远程…

Rust 字符串基本使用教程及代码演示

文章目录 一、基本使用教程1、字符串类型String&str 2、创建字符串创建String创建&str 3、字符串操作索引切片格式化字符串比较 4、字符串和集合5、字符串的错误处理6、参考链接 二、代码演示1、代码演示2、执行结果 一、基本使用教程 在Rust中&#xff0c;字符串是编…