SpringBoot实现接口数据的加解密功能

网友投稿 1002 2023-07-15

SpringBoot实现接口数据的加解密功能

SpringBoot实现接口数据的加解密功能

一、加密方案介绍

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

自定义消息转换器

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

劣势:仅能对同一类型的MediaType进行加解密操作,不灵活。

使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice

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

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

二、实现原理

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,

http:// Type targetType, Class extends HttpMessageConverter>> converterType);

@Nullable

Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,

Type targetType, Class extends HttpMessageConverter>> converterType);

}

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

protected 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 targetClass = (targetType instanceof Class ? (Class) targetType : null);

if (targetClass == null) {

ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);

targetClass = (Class) 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> converterType = (Class>) 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) 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 {

boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType);

@Nullable

T 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

org.springframework.boot

spring-boot-starter-web

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

com.alibaba

fastjson

1.2.60

请求参数解密拦截类

DecryptRequestBodyAdvice代码如下:

/**

* 请求参数 解密操作

* * @Author: java碎碎念

* @Date: 2019/10/24 21:31

*

*/

@Component

@ControllerAdvice(basePackages = "com.example.springbootencry.controller")

@Slf4j

public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

@Override

public boolean supports(MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> converterType) {

return true;

}

@Override

public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class extends HttpMessageConverter>> selectedConverterType) throws IOException {

return inputMessage;

}

@Override

public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class extends HttpMessageConverter>> converterType) {

String dealData = null;

try {

//解密操作

Map dataMap = (Map)body;

String srcData = dataMap.get("data");

dealData = DesUtil.decrypt(srcData);

} catch (Exception e) {

log.error("异常!", e);

}

return dealData;

}

@Override

public 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 {

@Override

public boolean supports(MethodParameter returnType, Class extends HttpMessageConverter>> converterType) {

return true;

}

@Override

public Object beforeBodyWrite(Object obj, MethodParameter returnType, MediaType selectedContentType,

Class extends HttpMessageConverter>> selectedConverterType, ServerHttpRequest serverHttpRequest,

ServerHttpResponse serverHttpResponse) {

//通过 ServerHttpRequest的实现类ServletServerHttpRequest 获得HttpServletRequest

ServletServerHttpRequest 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

总结

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Spring为IOC容器注入Bean的五种方式详解
下一篇:SpringBoot逻辑异常统一处理方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~