数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密、解密的方式。

本文目录

一、加密方案介绍二、实现原理三、实战四、测试五、踩到的坑

一、加密方案介绍

对接口的加密解密操作主要有下面两种方式:

  1. 自定义消息转换器

优势:仅需实现接口,配置简单。

  1. 使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice

优势:可以按照请求的Referrer、Header或url进行判断,按照特定需要进行加密解密。

比如在一个项目升级的时候,新开发功能的接口需要加解密,老功能模块走之前的逻辑不加密,这时候就只能选择上面的第二种方式了,下面主要介绍下第二种方式加密、解密的过程。

二、实现原理

RequestBodyAdvice可以理解为在@RequestBody之前需要进行的 操作,ResponseBodyAdvice可以理解为在@ResponseBody之后进行的操作,所以当接口需要加解密时,在使用@RequestBody接收前台参数之前可以先在RequestBodyAdvice的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@ResponseBody之后进入ResponseBodyAdvice的实现类中进行参数的加密。

RequestBodyAdvice处理请求的过程:

RequestBodyAdvice源码如下:

 public interface RequestBodyAdvice {boolean supports(MethodParameter methodParameter, Type targetType,Class<? extends HttpMessageConverter<?>> converterType);HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException;Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType);@NullableObject handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType);}

调用RequestBodyAdvice实现类的部分代码如下:

 protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {MediaType contentType;boolean noContentType = false;try {contentType = inputMessage.getHeaders().getContentType();}catch (InvalidMediaTypeException ex) {throw new HttpMediaTypeNotSupportedException(ex.getMessage());}if (contentType == null) {noContentType = true;contentType = MediaType.APPLICATION_OCTET_STREAM;}Class<?> contextClass = parameter.getContainingClass();Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);if (targetClass == null) {ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);targetClass = (Class<T>) resolvableType.resolve();}HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);Object body = NO_VALUE;EmptyBodyCheckingHttpInputMessage message;try {message = new EmptyBodyCheckingHttpInputMessage(inputMessage);for (HttpMessageConverter<?> converter : this.messageConverters) {Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();GenericHttpMessageConverter<?> genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :(targetClass != null && converter.canRead(targetClass, contentType))) {if (logger.isDebugEnabled()) {logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");}if (message.hasBody()) {HttpInputMessage msgToUse =getAdvice().beforeBodyRead(message, parameter, targetType, converterType);body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);}else {body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);}break;}}}catch (IOException ex) {throw new HttpMessageNotReadableException("I/O error while reading input message", ex);}if (body == NO_VALUE) {if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||(noContentType && !message.hasBody())) {return null;}throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);}return body;}

从上面源码可以到当converter.canRead()和message.hasBody()都为true的时候,会调用beforeBodyRead()和afterBodyRead()方法,所以我们在实现类的afterBodyRead()中添加解密代码即可。

ResponseBodyAdvice处理响应的过程:

ResponseBodyAdvice源码如下:

public interface ResponseBodyAdvice<T> {boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);@NullableT beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType,ServerHttpRequest request, ServerHttpResponse response);}

调用ResponseBodyAdvice实现类的部分代码如下:

if (selectedMediaType != null) {selectedMediaType = selectedMediaType.removeQualityValue();for (HttpMessageConverter<?> converter : this.messageConverters) {GenericHttpMessageConverter genericConverter =(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);if (genericConverter != null ?((GenericHttpMessageConverter) converter).canWrite(declaredType, valueType, selectedMediaType) :converter.canWrite(valueType, selectedMediaType)) {outputValue = (T) getAdvice().beforeBodyWrite(outputValue, returnType, selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),inputMessage, outputMessage);if (outputValue != null) {addContentDispositionHeader(inputMessage, outputMessage);if (genericConverter != null) {genericConverter.write(outputValue, declaredType, selectedMediaType, outputMessage);}else {((HttpMessageConverter) converter).write(outputValue, selectedMediaType, outputMessage);}if (logger.isDebugEnabled()) {logger.debug("Written [" + outputValue + "] as \"" + selectedMediaType +"\" using [" + converter + "]");}}return;}}}

从上面源码可以到当converter.canWrite()为true的时候,会调用beforeBodyWrite()方法,所以我们在实现类的beforeBodyWrite()中添加解密代码即可。

三、实战

新建一个spring boot项目spring-boot-encry,按照下面步骤操作。

  1. pom.xml中引入jar

  <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.60</version></dependency></dependencies>
  1. 请求参数解密拦截类

DecryptRequestBodyAdvice代码如下:

/*** 请求参数 解密操作** @Author: Java碎碎念* @Date: 2019/10/24 21:31**/
@Component
@ControllerAdvice(basePackages = "com.example.springbootencry.controller")
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {return inputMessage;}@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {String dealData = null;try {//解密操作Map<String,String> dataMap = (Map)body;String srcData = dataMap.get("data");dealData = DesUtil.decrypt(srcData);} catch (Exception e) {log.error("异常!", e);}return dealData;}@Overridepublic Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {log.info("3333");return var1;}}
  1. 响应参数加密拦截类

EncryResponseBodyAdvice代码如下:

/*** 请求参数 解密操作** @Author: Java碎碎念* @Date: 2019/10/24 21:31**/
@Component
@ControllerAdvice(basePackages = "com.example.springbootencry.controller")
@Slf4j
public class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return true;}@Overridepublic Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest,ServerHttpResponse serverHttpResponse) {//通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequestServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;//此处获取到request 是为了取到在拦截器里面设置的一个对象 是我项目需要,可以忽略HttpServletRequest request = sshr.getServletRequest();String returnStr = "";try {//添加encry header,告诉前端数据已加密serverHttpResponse.getHeaders().add("encry", "true");String srcData = JSON.toJSONString(obj);//加密returnStr = DesUtil.encrypt(srcData);log.info("接口={},原始数据={},加密后数据={}", request.getRequestURI(), srcData, returnStr);} catch (Exception e) {log.error("异常!", e);}return returnStr;}
  1. 新建controller类

TestController代码如下:

/*** @Author: Java碎碎念* @Date: 2019/10/24 21:40*/
@RestController
public class TestController {Logger log = LoggerFactory.getLogger(getClass());/*** 响应数据 加密*/@RequestMapping(value = "/sendResponseEncryData")public Result sendResponseEncryData() {Result result = Result.createResult().setSuccess(true);result.setDataValue("name", "Java碎碎念");result.setDataValue("encry", true);return result;}/*** 获取 解密后的 请求参数*/@RequestMapping(value = "/getRequestData")public Result getRequestData(@RequestBody Object object) {log.info("controller接收的参数object={}", object.toString());Result result = Result.createResult().setSuccess(true);return result;}
}
  1. 其他类在源码中,后面有github地址

四、测试

  1. 访问响应数据加密接口

使用postman发请求http://localhost:8888/sendResponseEncryData,可以看到返回数据已加密,请求截图如下:

响应数据加密截图

后台也打印相关的日志,内容如下:

接口=/sendResponseEncryData原始数据={"data":{"encry":true,"name":"Java碎碎念"},"success":true}加密后数据=vJc26g3SQRU9gAJdG7rhnAx6Ky/IhgioAgdwi6aLMMtyynAB4nEbMxvDsKEPNIa5bQaT7ZAImAL7
3VeicCuSTA==
  1. 访问请求数据解密接口

使用postman发请求http://localhost:8888/getRequestData,可以看到请求数据已解密,请求截图如下:

请求数据解密截图

后台也打印相关的日志,内容如下:

接收到原始请求数据={"data":"VwLvdE8N6FuSxn/jRrJavATopaBA3M1QEN+9bkuf2jPwC1eSofgahQ=="}解密后数据={"name":"Java碎碎念","des":"请求参数"}

五、踩到的坑

  1. 测试解密请求参数时候,请求体一定要有数据,否则不会调用实现类触发解密操作。

到此SpringBoot中如何灵活的实现接口数据的加解密功能的功能已经全部实现,有问题欢迎留言沟通哦!

完整源码地址: https://github.com/suisui2019/springboot-study

有道无术,术可成;有术无道,止于术

欢迎大家关注Java之道公众号

好文章,我在看❤️

SpringBoot中如何灵活的实现接口数据的加解密功能?相关推荐

  1. SpringBoot中如何灵活的实现接口数据的加解密...

    对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单. 劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接口RequestB ...

  2. boot数据加解密 spring_SpringBoot实现接口数据的加解密功能

    一.加密方案介绍 对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单. 劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接 ...

  3. SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!

    一.前言: 数据是企业的第四张名片,企业级开发中少不了数据的加密传输.为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余 ...

  4. 护网必备技能:Spring Boot 接口数据加解密 功能实现

    护网必备技能:Spring Boot 接口数据加解密 功能实现 文章目录 护网必备技能:Spring Boot 接口数据加解密 功能实现 1. 尽量少改动,不影响之前的业务逻辑: 2. 考虑到时间紧迫 ...

  5. Python-使用U盾完成数据的加解密(使用国密算法SKF接口)

    Python-使用U盾完成数据的加解密(使用国密算法SKF接口) 1-涉及的内容 2-动态库涉及的函数,及结构体 2.1 相关结构体 2.2 相关函数 3-Python实现 4-测试结果 5-UI可视 ...

  6. 使用JDK中的安全包对数据进行加解密

    本文以使用DES对称加密算法为例使用jdk对数据进行加密解密. 首先需要了解Provider类,它是jdk引入的密码服务提供者概念,实现了Java安全性的一部分或者全部.Provider 可能实现的服 ...

  7. 文件传输-对数据进行加解密的方法!

    由于项目安全要求,需要使用RSA算法对部分关键数据进行加密,并使用OAEPWithSHA-256AndMGF1对数据进行填充.通过搜索最终选择较为通用OpenSSL库,但OpenSSL的RSA算法默认 ...

  8. Spring Boot 接口参数自动加解密

    本文标题:Spring Boot 接口参数自动加解密 原始链接: https://www.shuibo.cn/102.html 许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链 ...

  9. java中密码修改核心代码_彻底告别加解密模块代码拷贝-JCE核心Cpiher详解

    前提 javax.crypto.Cipher,翻译为密码,其实叫做密码器更加合适.Cipher是JCA(Java Cryptographic Extension,Java加密扩展)的核心,提供基于多种 ...

最新文章

  1. Adobe公司Flex首席产品经理作序推荐
  2. MII 功能简介(论坛整理)
  3. 使用 Amazon Cloud WAN 构建您的全球网络
  4. LeetCode Coin Change(动态规划)
  5. 使用SQL DTS功能实现从DB/2向SQL Server传输数据
  6. RSA非对称加密算法Java实现
  7. VTK:几何对象之Tetrahedron
  8. PHP artisan auth,Php artisan make:auth命令未定义
  9. VM中Windows server 2012 R2系统安装SQL SERVER 2012的安装步骤
  10. Ubuntu中python调用SimpleITK来显示图像
  11. linux c语言编写聊天室mysql_Linux平台上用C语言实现与MySQL数据库的连接
  12. PHP指定字段的多维数组排序方法
  13. window10 tar解压
  14. 用两个栈实现队列(Java)
  15. 如何给Word中的图片添加题注
  16. python 列表长度_python求列表长度
  17. 「 C++ 通讯 」“Socket通信原理(TCP/IP)”研究
  18. python 爬虫3 新浪微博 爬虫 实战
  19. 外贸客户开发系列 ,让你在领英Linkedin客户开发上事半功倍
  20. 东方通 -- 如何安装、启动、停止、卸载东方通中间件

热门文章

  1. 考研山东省的计算机学校有哪些,山东考研哪个学校好考
  2. java 用面向接口编程的方式开发打印机_Java“打印机”模型理解面向接口编程。实现接口定义类,接口实现类,核心“业务”类分离...
  3. beanshell字符串替换_必知必会的操作Jmeter(十六)_beanshell实现字符串加密
  4. php 加载redise_php环境篇:redis服务编译安装
  5. python写文件格式转换程序_python实现txt文件格式转换为arff格式
  6. 九、XML和Json的特点
  7. QT中生成字符串md5的方法
  8. qt设置路径为应用程序启动路径
  9. LeetCode 445 分发饼干
  10. USACO Section 1.2 Greedy Gift Givers (简单查找)