SpringBoot中如何灵活的实现接口数据的加解密功能?
数据是企业的第四张名片,企业级开发中少不了数据的加密传输,所以本文介绍下SpringBoot中接口数据加密、解密的方式。
本文目录
一、加密方案介绍二、实现原理三、实战四、测试五、踩到的坑
一、加密方案介绍
对接口的加密解密操作主要有下面两种方式:
自定义消息转换器
优势:仅需实现接口,配置简单。
使用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,按照下面步骤操作。
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>
请求参数解密拦截类
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;}}
响应参数加密拦截类
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;}
新建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;}
}
其他类在源码中,后面有github地址
四、测试
访问响应数据加密接口
使用postman发请求http://localhost:8888/sendResponseEncryData,可以看到返回数据已加密,请求截图如下:
后台也打印相关的日志,内容如下:
接口=/sendResponseEncryData原始数据={"data":{"encry":true,"name":"Java碎碎念"},"success":true}加密后数据=vJc26g3SQRU9gAJdG7rhnAx6Ky/IhgioAgdwi6aLMMtyynAB4nEbMxvDsKEPNIa5bQaT7ZAImAL7
3VeicCuSTA==
访问请求数据解密接口
使用postman发请求http://localhost:8888/getRequestData,可以看到请求数据已解密,请求截图如下:
后台也打印相关的日志,内容如下:
接收到原始请求数据={"data":"VwLvdE8N6FuSxn/jRrJavATopaBA3M1QEN+9bkuf2jPwC1eSofgahQ=="}解密后数据={"name":"Java碎碎念","des":"请求参数"}
五、踩到的坑
测试解密请求参数时候,请求体一定要有数据,否则不会调用实现类触发解密操作。
到此SpringBoot中如何灵活的实现接口数据的加解密功能的功能已经全部实现,有问题欢迎留言沟通哦!
完整源码地址: https://github.com/suisui2019/springboot-study
有道无术,术可成;有术无道,止于术
欢迎大家关注Java之道公众号
好文章,我在看❤️
SpringBoot中如何灵活的实现接口数据的加解密功能?相关推荐
- SpringBoot中如何灵活的实现接口数据的加解密...
对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单. 劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接口RequestB ...
- boot数据加解密 spring_SpringBoot实现接口数据的加解密功能
一.加密方案介绍 对接口的加密解密操作主要有下面两种方式: 自定义消息转换器 优势:仅需实现接口,配置简单. 劣势:仅能对同一类型的MediaType进行加解密操作,不灵活. 使用spring提供的接 ...
- SpringBoot 基于RequestBodyAdvice 和 ResponseBodyAdvice 实现数据的加/解密(采用 RSA 算法 ),“船新版本”!
一.前言: 数据是企业的第四张名片,企业级开发中少不了数据的加密传输.为了预防请求数据被劫持篡改,一般都会对传输的数据进行加密操作,如果每个接口都由我们自己去手动加密和解密,那么工作量太大而且代码冗余 ...
- 护网必备技能:Spring Boot 接口数据加解密 功能实现
护网必备技能:Spring Boot 接口数据加解密 功能实现 文章目录 护网必备技能:Spring Boot 接口数据加解密 功能实现 1. 尽量少改动,不影响之前的业务逻辑: 2. 考虑到时间紧迫 ...
- Python-使用U盾完成数据的加解密(使用国密算法SKF接口)
Python-使用U盾完成数据的加解密(使用国密算法SKF接口) 1-涉及的内容 2-动态库涉及的函数,及结构体 2.1 相关结构体 2.2 相关函数 3-Python实现 4-测试结果 5-UI可视 ...
- 使用JDK中的安全包对数据进行加解密
本文以使用DES对称加密算法为例使用jdk对数据进行加密解密. 首先需要了解Provider类,它是jdk引入的密码服务提供者概念,实现了Java安全性的一部分或者全部.Provider 可能实现的服 ...
- 文件传输-对数据进行加解密的方法!
由于项目安全要求,需要使用RSA算法对部分关键数据进行加密,并使用OAEPWithSHA-256AndMGF1对数据进行填充.通过搜索最终选择较为通用OpenSSL库,但OpenSSL的RSA算法默认 ...
- Spring Boot 接口参数自动加解密
本文标题:Spring Boot 接口参数自动加解密 原始链接: https://www.shuibo.cn/102.html 许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链 ...
- java中密码修改核心代码_彻底告别加解密模块代码拷贝-JCE核心Cpiher详解
前提 javax.crypto.Cipher,翻译为密码,其实叫做密码器更加合适.Cipher是JCA(Java Cryptographic Extension,Java加密扩展)的核心,提供基于多种 ...
最新文章
- Adobe公司Flex首席产品经理作序推荐
- MII 功能简介(论坛整理)
- 使用 Amazon Cloud WAN 构建您的全球网络
- LeetCode Coin Change(动态规划)
- 使用SQL DTS功能实现从DB/2向SQL Server传输数据
- RSA非对称加密算法Java实现
- VTK:几何对象之Tetrahedron
- PHP artisan auth,Php artisan make:auth命令未定义
- VM中Windows server 2012 R2系统安装SQL SERVER 2012的安装步骤
- Ubuntu中python调用SimpleITK来显示图像
- linux c语言编写聊天室mysql_Linux平台上用C语言实现与MySQL数据库的连接
- PHP指定字段的多维数组排序方法
- window10 tar解压
- 用两个栈实现队列(Java)
- 如何给Word中的图片添加题注
- python 列表长度_python求列表长度
- 「 C++ 通讯 」“Socket通信原理(TCP/IP)”研究
- python 爬虫3 新浪微博 爬虫 实战
- 外贸客户开发系列 ,让你在领英Linkedin客户开发上事半功倍
- 东方通 -- 如何安装、启动、停止、卸载东方通中间件
热门文章
- 考研山东省的计算机学校有哪些,山东考研哪个学校好考
- java 用面向接口编程的方式开发打印机_Java“打印机”模型理解面向接口编程。实现接口定义类,接口实现类,核心“业务”类分离...
- beanshell字符串替换_必知必会的操作Jmeter(十六)_beanshell实现字符串加密
- php 加载redise_php环境篇:redis服务编译安装
- python写文件格式转换程序_python实现txt文件格式转换为arff格式
- 九、XML和Json的特点
- QT中生成字符串md5的方法
- qt设置路径为应用程序启动路径
- LeetCode 445 分发饼干
- USACO Section 1.2 Greedy Gift Givers (简单查找)