我有一个扩展AbstractHttpMessageConverter的消息转换器类,在这个类中我添加了加密和解密功能。同时,我公开了 Spring Boot 执行器运行状况检查端点,但是每当我访问此端点时总是会进行解密并最终抛出异常。如何禁用此执行器端点的加密和解密。
这是我的消息转换器片段。
package com.bdi.omnichannel.util;
import com.bdi.omnichannel.model.CustomError;
import com.bdi.omnichannel.model.external.DecryptHelperRequest;
import com.bdi.omnichannel.model.external.DecryptHelperResponse;
import com.bdi.omnichannel.model.external.EncryptHelperResponse;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Scanner;
public class Converter extends AbstractHttpMessageConverter<Object> {
private static final Logger logger = LoggerFactory.getLogger(Converter.class);
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private ExternalApiUtil externalApiUtil;
private boolean isRestTmplReq;
public Converter(ExternalApiUtil externalApiUtil, boolean isRestTmplReq) {
super(MediaType.APPLICATION_OCTET_STREAM, new MediaType("application", "*+octet-stream", DEFAULT_CHARSET), MediaType.APPLICATION_JSON, new MediaType("application", "*+json", DEFAULT_CHARSET));
this.externalApiUtil = externalApiUtil;
this.isRestTmplReq = isRestTmplReq;
}
@Override
protected boolean supports(Class<?> clazz) {
return true;
}
@Override
protected Object readInternal(Class<? extends Object> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
ObjectMapper objectMapper = new ObjectMapper();
String req = decrypt(toString(inputMessage.getBody()));
logger.debug("readInternal request {}", req);
logger.debug("readInternal isRestTmplReq {}", isRestTmplReq);
if (!isRestTmplReq) {
ABCContext.setContext(ABCConstant.KEY_MAP_DECRYPTED_REQUEST, req);
}
return objectMapper.readValue(req, clazz);
}
@Override
protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
String res = new ObjectMapper().writeValueAsString(o);
logger.debug("writeInternal response {}", res);
logger.debug("writeInternal isRestTmplReq {}", isRestTmplReq);
if (!isRestTmplReq) {
ABCContext.setContext(ABCConstant.KEY_MAP_DECRYPTED_RESPONSE, res);
}
if (BDIContext.has(ABCConstant.KEY_MAP_ENCRYPTED_KEY)) {
outputMessage.getHeaders().set("X-DRIB-ENCRYPTED-DATA", ABCContext.getValue(ABCConstant.KEY_MAP_ENCRYPTED_KEY).toString());
}
ResponseBuilder<Object> responseBuilderEmoney = null;
JSONParser<Object> jsonParser = null;
try {
byte[] response = encrypt(o);
if (response == null) {
responseBuilderEmoney = new ResponseBuilder<>();
jsonParser = new JSONParser<>(Object.class);
CustomError customError = new CustomError();
customError.setCode(ABCConstant.ENCRYPT_ERROR_CODE);
customError.setMessage(ABCConstant.ENCRYPT_ERROR_MESSAGE);
customError.setType(ABCConstant.ENCRYPT_ERROR_TYPE);
String responseJson = jsonParser.setJson(responseBuilderEmoney.buildResponse(Collections.singletonList(customError)));
response = responseJson.getBytes();
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_JSON);
if (!isRestTmplReq) {
ABCContext.setContext(ABCConstant.KEY_MAP_DECRYPTED_RESPONSE, responseJson);
}
} else {
outputMessage.getHeaders().setContentType(MediaType.APPLICATION_OCTET_STREAM);
}
if (ABCContext.has(ABCConstant.KEY_MAP_ENCR_SIGNATURE_DATA)) {
outputMessage.getHeaders().set("X-DRIB-SIGNATURE-DATA", ABCContext.getValue(ABCConstant.KEY_MAP_ENCR_SIGNATURE_DATA).toString());
}
outputMessage.getBody().write(response);
} finally {
responseBuilderEmoney = null;
jsonParser = null;
}
}
private String decrypt(String inputMessage) {
DecryptHelperRequest decryptHelperRequest = null;
DecryptHelperResponse decryptHelperResponse = null;
try {
decryptHelperRequest = new DecryptHelperRequest();
decryptHelperRequest.setParams(Collections.singletonList(inputMessage));
decryptHelperResponse = externalApiUtil.doDecrypt(decryptHelperRequest);
if (decryptHelperResponse != null && decryptHelperResponse.getResults().size() > 0) {
return decryptHelperResponse.getResults().get(0);
}
return null;
} finally {
decryptHelperRequest = null;
decryptHelperResponse = null;
}
}
private byte[] encrypt(Object o) throws JsonProcessingException {
EncryptHelperResponse encryptHelperResponse = null;
try {
encryptHelperResponse = externalApiUtil.doEncrypt(o);
if (encryptHelperResponse != null && (encryptHelperResponse.getResults() != null && encryptHelperResponse.getResults().size() > 0)) {
return encryptHelperResponse.getResults().get(0).getBytes();
}
return null;
} finally {
encryptHelperResponse = null;
}
}
private static String toString(InputStream inputStream) {
Scanner scanner = null;
try {
scanner = new Scanner(inputStream, "UTF-8");
return scanner.useDelimiter("\\A").next();
} finally {
scanner.close();
}
}
}
仅当您的团队中有安全专家、Servlet 专家和 Spring 专家时,我才建议使用自定义加密解决方案。
Spring 转换器对此来说是一个糟糕的选择,因为它们对原始请求知之甚少。他们只知道媒体类型,因此如果您想继续使用此解决方案,您应该使用自定义媒体类型(HTTP 请求中的内容类型)进行自定义加密。
更好的解决方案是将加密逻辑移至您可以访问和控制原始请求的位置,例如,在 servlet 过滤器中。在自定义过滤器中,您可以排除不想解密的路径,并在 Spring 开始解析请求之前解密请求。