SpringBoot实现接口数据的加解密功能
一、加密方案介绍
对接口的加密解密操作主要有下面两种方式:
自定义消息转换器
优势:仅需实现接口,配置简单。
劣势:仅能对同一类型的MediaType进行加解密操作,不灵活。
使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice
优势:可以按照请求的Referrer、Header或url进行判断,按照特定需要进行加密解密。
比如在一个项目升级的时候,新开发功能的接口需要加解密,老功能模块走之前的逻辑不加密,这时候就只能选择上面的第二种方式了,下面主要介绍下第二种方式加密、解密的过程。
二、实现原理
RequestBodyAdvice可以理解为在@RequestBody之前需要进行的操作,ResponseBodyAdvice可以理解为在@ResponseBody之后进行的操作,所以当接口需要加解密时,在使用@RequestBody接收前台参数之前可以先在RequestBodyAdvice的实现类中进行参数的解密,当操作结束需要返回数据时,可以在@ResponseBody之后进入ResponseBodyAdvice的实现类中进行参数的加密。
RequestBodyAdvice处理请求的过程:
RequestBodyAdvice源码如下:
publicinterfaceRequestBodyAdvice{ booleansupports(MethodParametermethodParameter,TypetargetType, Class>converterType); HttpInputMessagebeforeBodyRead(HttpInputMessageinputMessage,MethodParameterparameter, TypetargetType,Class>converterType)throwsIOException; ObjectafterBodyRead(Objectbody,HttpInputMessageinputMessage,MethodParameterparameter, TypetargetType,Class>converterType); @Nullable ObjecthandleEmptyBody(@NullableObjectbody,HttpInputMessageinputMessage,MethodParameterparameter, TypetargetType,Class>converterType); }
调用RequestBodyAdvice实现类的部分代码如下:
protectedObjectreadWithMessageConverters(HttpInputMessageinputMessage,MethodParameterparameter, TypetargetType)throwsIOException,HttpMediaTypeNotSupportedException,HttpMessageNotReadableException{ MediaTypecontentType; booleannoContentType=false; try{ contentType=inputMessage.getHeaders().getContentType(); } catch(InvalidMediaTypeExceptionex){ thrownewHttpMediaTypeNotSupportedException(ex.getMessage()); } if(contentType==null){ noContentType=true; contentType=MediaType.APPLICATION_OCTET_STREAM; } Class>contextClass=parameter.getContainingClass(); Class targetClass=(targetTypeinstanceofClass?(Class )targetType:null); if(targetClass==null){ ResolvableTyperesolvableType=ResolvableType.forMethodParameter(parameter); targetClass=(Class )resolvableType.resolve(); } HttpMethodhttpMethod=(inputMessageinstanceofHttpRequest?((HttpRequest)inputMessage).getMethod():null); Objectbody=NO_VALUE; EmptyBodyCheckingHttpInputMessagemessage; try{ message=newEmptyBodyCheckingHttpInputMessage(inputMessage); for(HttpMessageConverter>converter:this.messageConverters){ Class >converterType=(Class >)converter.getClass(); GenericHttpMessageConverter>genericConverter= (converterinstanceofGenericHttpMessageConverter?(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()){ HttpInputMessagemsgToUse= 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(IOExceptionex){ thrownewHttpMessageNotReadableException("I/Oerrorwhilereadinginputmessage",ex); } if(body==NO_VALUE){ if(httpMethod==null||!SUPPORTED_METHODS.contains(httpMethod)|| (noContentType&&!message.hasBody())){ returnnull; } thrownewHttpMediaTypeNotSupportedException(contentType,this.allSupportedMediaTypes); } returnbody; }
从上面源码可以到当converter.canRead()和message.hasBody()都为true的时候,会调用beforeBodyRead()和afterBodyRead()方法,所以我们在实现类的afterBodyRead()中添加解密代码即可。
ResponseBodyAdvice处理响应的过程:
ResponseBodyAdvice源码如下:
publicinterfaceResponseBodyAdvice{ booleansupports(MethodParameterreturnType,Class>converterType); @Nullable TbeforeBodyWrite(@NullableTbody,MethodParameterreturnType,MediaTypeselectedContentType, Class>selectedConverterType, ServerHttpRequestrequest,ServerHttpResponseresponse); }
调用ResponseBodyAdvice实现类的部分代码如下:
if(selectedMediaType!=null){ selectedMediaType=selectedMediaType.removeQualityValue(); for(HttpMessageConverter>converter:this.messageConverters){ GenericHttpMessageConvertergenericConverter= (converterinstanceofGenericHttpMessageConverter?(GenericHttpMessageConverter>)converter:null); if(genericConverter!=null? ((GenericHttpMessageConverter)converter).canWrite(declaredType,valueType,selectedMediaType): converter.canWrite(valueType,selectedMediaType)){ outputValue=(T)getAdvice().beforeBodyWrite(outputValue,returnType,selectedMediaType, (Class>)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()中添加解密代码即可。
三、实战
新建一个springboot项目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/2421:31 * */ @Component @ControllerAdvice(basePackages="com.example.springbootencry.controller") @Slf4j publicclassDecryptRequestBodyAdviceimplementsRequestBodyAdvice{ @Override publicbooleansupports(MethodParametermethodParameter,TypetargetType,Class>converterType){ returntrue; } @Override publicHttpInputMessagebeforeBodyRead(HttpInputMessageinputMessage,MethodParametermethodParameter,TypetargetType,Class>selectedConverterType)throwsIOException{ returninputMessage; } @Override publicObjectafterBodyRead(Objectbody,HttpInputMessageinputMessage,MethodParameterparameter,TypetargetType,Class>converterType){ StringdealData=null; try{ //解密操作 MapdataMap=(Map)body; StringsrcData=dataMap.get("data"); dealData=DesUtil.decrypt(srcData); }catch(Exceptione){ log.error("异常!",e); } returndealData; } @Override publicObjecthandleEmptyBody(@NullableObjectvar1,HttpInputMessagevar2,MethodParametervar3,Typevar4,Class>var5){ log.info("3333"); returnvar1; } }
响应参数加密拦截类
EncryResponseBodyAdvice代码如下:
/** *请求参数解密操作 * *@Author:Java碎碎念 *@Date:2019/10/2421:31 * */ @Component @ControllerAdvice(basePackages="com.example.springbootencry.controller") @Slf4j publicclassEncryResponseBodyAdviceimplementsResponseBodyAdvice
新建controller类
TestController代码如下:
/***@Author:Java碎碎念 *@Date:2019/10/2421:40 */ @RestController publicclassTestController{ Loggerlog=LoggerFactory.getLogger(getClass()); /** *响应数据加密 */ @RequestMapping(value="/sendResponseEncryData") publicResultsendResponseEncryData(){ Resultresult=Result.createResult().setSuccess(true); result.setDataValue("name","Java碎碎念"); result.setDataValue("encry",true); returnresult; } /** *获取解密后的请求参数 */ @RequestMapping(value="/getRequestData") publicResultgetRequestData(@RequestBodyObjectobject){ log.info("controller接收的参数object={}",object.toString()); Resultresult=Result.createResult().setSuccess(true); returnresult; } }
其他类在源码中,后面有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
总结
以上所述是小编给大家介绍的SpringBoot实现接口数据的加解密功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对毛票票网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。