SpringBoot實(shí)現(xiàn)接口數(shù)據(jù)的加解密功能
一、加密方案介紹
對(duì)接口的加密解密操作主要有下面兩種方式:
自定義消息轉(zhuǎn)換器
優(yōu)勢(shì):僅需實(shí)現(xiàn)接口,配置簡(jiǎn)單。
劣勢(shì):僅能對(duì)同一類(lèi)型的MediaType進(jì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)類(lèi)中進(jìn)行參數(shù)的解密,當(dāng)操作結(jié)束需要返回?cái)?shù)據(jù)時(shí),可以在@ResponseBody之后進(jìn)入ResponseBodyAdvice的實(shí)現(xiàn)類(lèi)中進(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);
@Nullable
Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType, Class<? extends HttpMessageConverter<?>> converterType);
}
調(diào)用RequestBodyAdvice實(shí)現(xiàn)類(lèi)的部分代碼如下:
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)類(lèi)的afterBodyRead()中添加解密代碼即可。
ResponseBodyAdvice處理響應(yīng)的過(guò)程:
ResponseBodyAdvice源碼如下:
public interface ResponseBodyAdvice<T> {
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);
}
調(diào)用ResponseBodyAdvice實(shí)現(xiàn)類(lèi)的部分代碼如下:
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)類(lèi)的beforeBodyWrite()中添加解密代碼即可。
三、實(shí)戰(zhàn)
新建一個(gè)spring boot項(xiàng)目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>
請(qǐng)求參數(shù)解密攔截類(lèi)
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 {
@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<String,String> 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;
}
}
響應(yīng)參數(shù)加密攔截類(lèi)
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> {
@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) {
//通過(guò) ServerHttpRequest的實(shí)現(xiàn)類(lèi)ServletServerHttpRequest 獲得HttpServletRequest
ServletServerHttpRequest 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類(lèi)
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;
}
}
其他類(lèi)在源碼中,后面有g(shù)ithub地址
四、測(cè)試
訪(fǎng)問(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==
訪(fǎng)問(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)類(lèi)觸發(fā)解密操作。
到此SpringBoot中如何靈活的實(shí)現(xiàn)接口數(shù)據(jù)的加解密功能的功能已經(jīng)全部實(shí)現(xiàn),有問(wèn)題歡迎留言溝通哦!
完整源碼地址: https://github.com/suisui2019/springboot-study
總結(jié)
以上所述是小編給大家介紹的SpringBoot實(shí)現(xiàn)接口數(shù)據(jù)的加解密功能,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
如果你覺(jué)得本文對(duì)你有幫助,歡迎轉(zhuǎn)載,煩請(qǐng)注明出處,謝謝!
相關(guān)文章
Spring Boot項(xiàng)目集成UidGenerato的方法步驟
這篇文章主要介紹了Spring Boot項(xiàng)目集成UidGenerato的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
SpringBoot操作Mongodb的實(shí)現(xiàn)示例
本文主要介紹了SpringBoot操作Mongodb的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
JavaWeb ServletConfig作用及原理分析講解
ServletConfig對(duì)象,叫Servlet配置對(duì)象。主要用于加載配置文件的初始化參數(shù)。我們知道一個(gè)Web應(yīng)用里面可以有多個(gè)servlet,如果現(xiàn)在有一份數(shù)據(jù)需要傳給所有的servlet使用,那么我們就可以使用ServletContext對(duì)象了2022-10-10
SpringBoot下token短信驗(yàn)證登入登出權(quán)限操作(token存放redis,ali短信接口)
這篇文章主要介紹了SpringBoot下token短信驗(yàn)證登入登出權(quán)限操作(token存放redis,ali短信接口),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
Spring數(shù)據(jù)訪(fǎng)問(wèn)模板化方法
今天小編就為大家分享一篇關(guān)于Spring數(shù)據(jù)訪(fǎng)問(wèn)模板化,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01
Java 中的 BufferedReader 介紹_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
BufferedReader 是緩沖字符輸入流。它繼承于Reader。接下來(lái)通過(guò)本文給大家介紹BufferedReader的相關(guān)知識(shí),需要的朋友參考下吧2017-05-05
Java調(diào)用Pytorch模型實(shí)現(xiàn)圖像識(shí)別
這篇文章主要為大家詳細(xì)介紹了Java如何調(diào)用Pytorch實(shí)現(xiàn)圖像識(shí)別功能,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解一下2023-06-06
Java雙向鏈表按照順序添加節(jié)點(diǎn)的方法實(shí)例
這篇文章主要給大家介紹了關(guān)于Java雙向鏈表按照順序添加節(jié)點(diǎn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
springBoot定時(shí)任務(wù)處理類(lèi)的實(shí)現(xiàn)代碼
這篇文章主要介紹了springBoot定時(shí)任務(wù)處理類(lèi),需要的朋友可以參考下2018-06-06
詳解springboot解決第三方依賴(lài)jar包的問(wèn)題
本篇文章主要介紹了詳解springboot解決第三方依賴(lài)jar包的問(wèn)題,解決了第三方依賴(lài)jar包的問(wèn)題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09

