javascript
SpringBoot中如何灵活的实现接口数据的加解密功能?
數(shù)據(jù)是企業(yè)的第四張名片,企業(yè)級(jí)開(kāi)發(fā)中少不了數(shù)據(jù)的加密傳輸,所以本文介紹下SpringBoot中接口數(shù)據(jù)加密、解密的方式。
本文目錄
一、加密方案介紹二、實(shí)現(xiàn)原理三、實(shí)戰(zhàn)四、測(cè)試五、踩到的坑
一、加密方案介紹
對(duì)接口的加密解密操作主要有下面兩種方式:
自定義消息轉(zhuǎn)換器
優(yōu)勢(shì):僅需實(shí)現(xiàn)接口,配置簡(jiǎn)單。
使用spring提供的接口RequestBodyAdvice和ResponseBodyAdvice
優(yōu)勢(shì):可以按照請(qǐng)求的Referrer、Header或url進(jìn)行判斷,按照特定需要進(jìn)行加密解密。
比如在一個(gè)項(xiàng)目升級(jí)的時(shí)候,新開(kāi)發(fā)功能的接口需要加解密,老功能模塊走之前的邏輯不加密,這時(shí)候就只能選擇上面的第二種方式了,下面主要介紹下第二種方式加密、解密的過(guò)程。
二、實(shí)現(xiàn)原理
RequestBodyAdvice可以理解為在@RequestBody之前需要進(jìn)行的 操作,ResponseBodyAdvice可以理解為在@ResponseBody之后進(jìn)行的操作,所以當(dāng)接口需要加解密時(shí),在使用@RequestBody接收前臺(tái)參數(shù)之前可以先在RequestBodyAdvice的實(shí)現(xiàn)類中進(jìn)行參數(shù)的解密,當(dāng)操作結(jié)束需要返回?cái)?shù)據(jù)時(shí),可以在@ResponseBody之后進(jìn)入ResponseBodyAdvice的實(shí)現(xiàn)類中進(jìn)行參數(shù)的加密。
RequestBodyAdvice處理請(qǐng)求的過(guò)程:
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);}調(diào)用RequestBodyAdvice實(shí)現(xiàn)類的部分代碼如下:
?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;}從上面源碼可以到當(dāng)converter.canRead()和message.hasBody()都為true的時(shí)候,會(huì)調(diào)用beforeBodyRead()和afterBodyRead()方法,所以我們?cè)趯?shí)現(xiàn)類的afterBodyRead()中添加解密代碼即可。
ResponseBodyAdvice處理響應(yīng)的過(guò)程:
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);}調(diào)用ResponseBodyAdvice實(shí)現(xiàn)類的部分代碼如下:
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;}}}從上面源碼可以到當(dāng)converter.canWrite()為true的時(shí)候,會(huì)調(diào)用beforeBodyWrite()方法,所以我們?cè)趯?shí)現(xiàn)類的beforeBodyWrite()中添加解密代碼即可。
三、實(shí)戰(zhàn)
新建一個(gè)spring boot項(xiàng)目spring-boot-encry,按照下面步驟操作。
pom.xml中引入jar
請(qǐng)求參數(shù)解密攔截類
DecryptRequestBodyAdvice代碼如下:
/***?請(qǐng)求參數(shù)?解密操作**?@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;}}響應(yīng)參數(shù)加密攔截類
EncryResponseBodyAdvice代碼如下:
/***?請(qǐng)求參數(shù)?解密操作**?@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)?{//通過(guò)?ServerHttpRequest的實(shí)現(xiàn)類ServletServerHttpRequest?獲得HttpServletRequestServletServerHttpRequest?sshr?=?(ServletServerHttpRequest)?serverHttpRequest;//此處獲取到request?是為了取到在攔截器里面設(shè)置的一個(gè)對(duì)象?是我項(xiàng)目需要,可以忽略HttpServletRequest?request?=?sshr.getServletRequest();String?returnStr?=?"";try?{//添加encry?header,告訴前端數(shù)據(jù)已加密serverHttpResponse.getHeaders().add("encry",?"true");String?srcData?=?JSON.toJSONString(obj);//加密returnStr?=?DesUtil.encrypt(srcData);log.info("接口={},原始數(shù)據(jù)={},加密后數(shù)據(jù)={}",?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());/***?響應(yīng)數(shù)據(jù)?加密*/@RequestMapping(value?=?"/sendResponseEncryData")public?Result?sendResponseEncryData()?{Result?result?=?Result.createResult().setSuccess(true);result.setDataValue("name",?"Java碎碎念");result.setDataValue("encry",?true);return?result;}/***?獲取?解密后的?請(qǐng)求參數(shù)*/@RequestMapping(value?=?"/getRequestData")public?Result?getRequestData(@RequestBody?Object?object)?{log.info("controller接收的參數(shù)object={}",?object.toString());Result?result?=?Result.createResult().setSuccess(true);return?result;} }其他類在源碼中,后面有g(shù)ithub地址
四、測(cè)試
訪問(wèn)響應(yīng)數(shù)據(jù)加密接口
使用postman發(fā)請(qǐng)求http://localhost:8888/sendResponseEncryData,可以看到返回?cái)?shù)據(jù)已加密,請(qǐng)求截圖如下:
響應(yīng)數(shù)據(jù)加密截圖后臺(tái)也打印相關(guān)的日志,內(nèi)容如下:
接口=/sendResponseEncryData原始數(shù)據(jù)={"data":{"encry":true,"name":"Java碎碎念"},"success":true}加密后數(shù)據(jù)=vJc26g3SQRU9gAJdG7rhnAx6Ky/IhgioAgdwi6aLMMtyynAB4nEbMxvDsKEPNIa5bQaT7ZAImAL7 3VeicCuSTA==訪問(wèn)請(qǐng)求數(shù)據(jù)解密接口
使用postman發(fā)請(qǐng)求http://localhost:8888/getRequestData,可以看到請(qǐng)求數(shù)據(jù)已解密,請(qǐng)求截圖如下:
請(qǐng)求數(shù)據(jù)解密截圖后臺(tái)也打印相關(guān)的日志,內(nèi)容如下:
接收到原始請(qǐng)求數(shù)據(jù)={"data":"VwLvdE8N6FuSxn/jRrJavATopaBA3M1QEN+9bkuf2jPwC1eSofgahQ=="}解密后數(shù)據(jù)={"name":"Java碎碎念","des":"請(qǐng)求參數(shù)"}五、踩到的坑
測(cè)試解密請(qǐng)求參數(shù)時(shí)候,請(qǐng)求體一定要有數(shù)據(jù),否則不會(huì)調(diào)用實(shí)現(xiàn)類觸發(fā)解密操作。
到此SpringBoot中如何靈活的實(shí)現(xiàn)接口數(shù)據(jù)的加解密功能的功能已經(jīng)全部實(shí)現(xiàn),有問(wèn)題歡迎留言溝通哦!
完整源碼地址: https://github.com/suisui2019/springboot-study
有道無(wú)術(shù),術(shù)可成;有術(shù)無(wú)道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號(hào)
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的SpringBoot中如何灵活的实现接口数据的加解密功能?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: NYOJ 679 The Weig
- 下一篇: NYOJ 252 01串 d