Using annotation + RequestBodyAdvice to realize the encryption and decryption mode of http request content
- 2021-10-13 07:27:33
- OfStack
Annotations are mainly used to specify those controller methods that need encryption and decryption
The implementation is relatively simple
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretAnnotation {
boolean encode() default false;
boolean decode() default false;
}
Add annotations to the methods of controller when using
@PostMapping("/preview")
@SecretAnnotation(decode = true)
public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO) {
return contractSignService.previewContract(fillContractDTO);
}
Request data from binary stream to class object data, for encrypted data, need to be decrypted before binary stream is processed, otherwise, when converted to class object, error will be reported because of data format mismatch.
Therefore, the beforeBodyRead method of RequestBodyAdvice is used for processing.
@Slf4j
@RestControllerAdvice
public class MyRequestControllerAdvice implements RequestBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return methodParameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
@Autowired
private MySecretUtil mySecretUtil;
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {
SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);
if (secretAnnotation.decode()) {
return new HttpInputMessage() {
@Override
public InputStream getBody() throws IOException {
List<String> appIdList = httpInputMessage.getHeaders().get("appId");
if (appIdList.isEmpty()){
throw new RuntimeException(" Request header missing appID");
}
String appId = appIdList.get(0);
String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");
bodyStr = mySecretUtil.decode(bodyStr,appId);
return IOUtils.toInputStream(bodyStr,"utf-8");
}
@Override
public HttpHeaders getHeaders() {
return httpInputMessage.getHeaders();
}
};
}
}
return httpInputMessage;
}
@Override
public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
return o;
}
}
The content of mySecretUtil. decode (bodyStr, appId) is to search for the secret key in the database through AppID in the request header, then decrypt it and return the decrypted string.
Then, through the tool class IOUtils provided in common. io package, the string is converted into inputstream stream, replacing HttpInputMessage, and returning an HttpInputMessage with body data as decrypted binary stream.
How Stringboot RequestBodyAdvice Interface Realizes Request Response Encryption and Decryption
In actual projects, we often need to do some operations before and after the request, such as parameter decryption/return result encryption, printing request parameters and return result log, etc. These non-business things, we don't want to write in the controller method, resulting in poor code repetition readability. Here, let's talk about using @ ControllerAdvice, RequestBodyAdvice, ResponseBodyAdvice to process before and after requests (AOP in essence) to log the parameters of every request and return results.
1. Encryption and decryption tool class
package com.linkus.common.utils;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Security;
import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Aes {
/**
*
* @author ngh
* AES128 Algorithm
*
* CBC Mode
*
* PKCS7Padding Fill mode
*
* CBC The schema needs to add an offset parameter iv , must 16 Bit
* Key sessionKey , must 16 Bit
*
* Between java Not supported PKCS7Padding , support only PKCS5Padding But PKCS7Padding And PKCS5Padding It doesn't make any difference
* To implement the java End use PKCS7Padding Fill, need to use bouncycastle Component to implement
*/
private String sessionKey=" Encryption and decryption key ";
// Offset 16 Bit
private static String iv=" Offset ";
// Algorithm name
final String KEY_ALGORITHM = "AES";
// Encryption and decryption algorithm / Mode / Filling mode
final String algorithmStr = "AES/CBC/PKCS7Padding";
// Encryption and decryption Key 16 Bit
byte[] ivByte;
byte[] keybytes;
private Key key;
private Cipher cipher;
boolean isInited = false;
public void init() {
// If the key is insufficient 16 Bit, then make up . This if The content in is very important
keybytes = iv.getBytes();
ivByte = iv.getBytes();
Security.addProvider(new BouncyCastleProvider());
// Convert into JAVA Key format of
key = new SecretKeySpec(keybytes, KEY_ALGORITHM);
try {
// Initialization cipher
cipher = Cipher.getInstance(algorithmStr, "BC");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Encryption method
*
* @param content
* String to encrypt
* Encryption key
* @return
*/
public String encrypt(String content) {
byte[] encryptedText = null;
byte[] contentByte = content.getBytes();
init();
try {
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(ivByte));
encryptedText = cipher.doFinal(contentByte);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(Hex.encode(encryptedText));
}
/**
* Decryption method
*
* @param encryptedData
* String to decrypt
* Decryption key
* @return
*/
public String decrypt(String encryptedData) {
byte[] encryptedText = null;
byte[] encryptedDataByte = Hex.decode(encryptedData);
init();
try {
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivByte));
encryptedText = cipher.doFinal(encryptedDataByte);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return new String(encryptedText);
}
public static void main(String[] args) {
Aes aes = new Aes();
String a="{\n" +
"\"distance\":\"1000\",\n" +
"\"longitude\":\"28.206471\",\n" +
"\"latitude\":\"112.941301\"\n" +
"}";
// Encrypted string
//String content = " Meng Fei runs fast ";
// System.out.println(" Before encryption: " + content);
// System.out.println(" Encryption key: " + new String(keybytes));
// Encryption method
String enc = aes.encrypt(a);
System.out.println(" Encrypted content: " + enc);
String dec="";
// Decryption method
try {
dec = aes.decrypt(enc);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(" Decrypted content: " + dec);
}
}
Step 2 Request decryption
The front-end page sends the ciphertext. We need to decrypt the ciphertext before Controller gets the request and then send it to Controller
package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.constant.KPlatResponseCode;
import com.linkus.common.exception.CustomException;
import com.linkus.common.exception.JTransException;
import com.linkus.common.service.util.MyHttpInputMessage;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.http.HttpHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.lang.reflect.Type;
/**
* Request parameter Decryption operation
*
* @Author: Java Broken thoughts
* @Date: 2019/10/24 21:31
*
*/
@Component
// You can configure to specify the package to be decrypted, supporting multiple
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {
Logger log = LoggerFactory.getLogger(getClass());
Aes aes=new Aes();
@Override
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
//true Turn on the function, false Turn off this feature
return true;
}
// Process the request before reading it
@Override
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> selectedConverterType) throws IOException {
// Get request data
String string = "";
BufferedReader bufferedReader = null;
InputStream inputStream = inputMessage.getBody();
// This request In fact, it is a reference You can get the stream from here
// Insert parameters and put them HttpInputMessage Inside The return value of this method is also HttpInputMessage
try {
string=getRequestBodyStr(inputStream,bufferedReader);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
/***************** Decrypt start*******************/
String decode = null;
if(HttpHelper.isEncrypted(inputMessage.getHeaders())){
try {
// // Decryption operation
//Map<String,String> dataMap = (Map)body;
//log.info(" The original request data is received ={}", string);
// inputData Is the data source to be encrypted and decrypted
// Decryption
decode= aes.decrypt(string);
//log.info(" Decrypted data ={}",decode);
} catch (Exception e ) {
log.error(" Encryption and decryption error :",e);
throw new CustomException(KPlatResponseCode.MSG_DECRYPT_TIMEOUT,KPlatResponseCode.CD_DECRYPT_TIMEOUT);
}
// Put the data in the object we encapsulate
}else{
decode = string;
}
// log.info(" Received request data ={}", decode);
// log.info(" Interface request address {}",((HttpServletRequest)inputMessage).getRequestURI());
return new MyHttpInputMessage(inputMessage.getHeaders(), new ByteArrayInputStream(decode.getBytes("UTF-8")));
}
@Override
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
@Override
public Object handleEmptyBody(@Nullable Object var1, HttpInputMessage var2, MethodParameter var3, Type var4, Class<? extends HttpMessageConverter<?>> var5) {
return var1;
}
// A method written by yourself, not an interface method, handles ciphertext
public String getRequestBodyStr( InputStream inputStream,BufferedReader bufferedReader) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
stringBuilder.append("");
}
String string = stringBuilder.toString();
return string;
}
}
Step 3 Encrypt the response
Encrypt the response returned to the front end to ensure the security of data
package com.linkus.common.filter;
import com.alibaba.fastjson.JSON;
import com.linkus.common.utils.Aes;
import com.linkus.common.utils.DesUtil;
import com.linkus.common.utils.http.HttpHelper;
import io.swagger.models.auth.In;
import lombok.experimental.Helper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Request parameter Encryption operation
*
* @Author: Java Broken thoughts
* @Date: 2019/10/24 21:31
*
*/
@Component
@ControllerAdvice(basePackages = {"com.linkus.project"})
@Slf4j
public class EncryResponseBodyAdvice implements ResponseBodyAdvice<Object> {
Logger log = LoggerFactory.getLogger(getClass());
Aes aes=new Aes();
@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) {
String returnStr = "";
Object retObj = null;
log.info(" Interface request address {}",serverHttpRequest.getURI());
// Log filtering
//retObj=infofilter.getInfoFilter(returnType,obj);
if(HttpHelper.isEncrypted(serverHttpRequest.getHeaders())) {
try {
// Add encry header Tells the front end that the data is encrypted
//serverHttpResponse.getHeaders().add("infoe", "e=a");
// Get request data
String srcData = JSON.toJSONString(obj);
// Encryption
returnStr = aes.encrypt(srcData).replace("\r\n", "");
//log.info(" Raw data ={}, Encrypted data ={}", obj, returnStr);
return returnStr;
} catch (Exception e) {
log.error(" Abnormal! ", e);
}
}
log.info(" Raw data ={}",JSON.toJSONString(obj));
return obj;
}
}