Service scenario: The client encrypts JSON data, encodes it into a Base64 string, and submits it to the server. The server then decrypts it. Using @RequestBodyAdvice, you can easily do this without changing any Controller code.

I wrote a previous post about using @responseBodyAdvice to unify the response data. Demonstrates AES encryption for json responses to clients using ResponseBodyAdvice.

RequestBodyAdvice interface

This interface defines a set of methods that can be used before and after the request body data is converted by HttpMessageConverter. Execute some logical code. It’s usually used for decryption.

import java.io.IOException;
import java.lang.reflect.Type;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;

public interface RequestBodyAdvice {

	/** * This method is used to determine whether to execute the beforeBodyRead method * for the current request@paramHandler method argument object *@paramHandler method parameter type *@paramThe Http message converter class type that will be used *@returnReturn true if beforeBodyRead */ is executed
	boolean supports(MethodParameter methodParameter, Type targetType, Class
       > converterType);

	/** * is executed before the Http message converter executes the conversion@paramClient request data *@paramHandler method argument object *@paramHandler method parameter type *@paramThe Http message converter class type that will be used *@returnReturn a custom HttpInputMessage */
	HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType) throws IOException;

	/** * performs the conversion in the Http message converter, and then executes the *@paramConverted object *@paramClient request data *@paramHandler method parameter type *@paramHandler method parameter type *@paramThe Http message converter class type used *@returnReturn a new object */
	Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType);

	/** * same as above, but this method handles the case where the body is empty */
	@Nullable
	Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType);


}
Copy the code

The core method is supports, which returns a Boolean value that determines whether to execute the beforeBodyRead method. Our main logic is to decrypt the client’s request body in the beforeBodyRead method.

RequestBodyAdviceAdapter

An adapter abstract class that implements the RequestBodyAdvice interface

import java.io.IOException;
import java.lang.reflect.Type;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;

public abstract class RequestBodyAdviceAdapter implements RequestBodyAdvice {
	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType)
			throws IOException {

		return inputMessage;
	}

	@Override
	public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType) {

		return body;
	}

	@Override
	@Nullable
	public Object handleEmptyBody(@Nullable Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType) {

		returnbody; }}Copy the code

Demo: Decrypt the AES encryption request body of the client

The client uses the AES algorithm to encrypt the JSON data using the AES (128-bit key) and encodes the DATA into a Base64 string as the request body to request the server. The server does the decryption in RequestBodyAdvice.

RequestBodyDecodeAdvice

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

@RestControllerAdvice
public class RequestBodyDecodeAdvice extends RequestBodyAdviceAdapter {

	/** * 128-bit AESkey */
	private static final byte[] AES_KEY = "1111111111111111".getBytes(StandardCharsets.US_ASCII);

	@Override
	public boolean supports(MethodParameter methodParameter, Type targetType, Class
       > converterType) {
		/** * The system uses Gson as the Http message converter for JSON data */
		return GsonHttpMessageConverter.class.isAssignableFrom(converterType);
	}

	@Override
	public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class
       > converterType) throws IOException {

		// Read the encrypted request body
		byte[] body = new byte[inputMessage.getBody().available()];
		inputMessage.getBody().read(body);

		try {
			// Use AES for decryption
			body = this.decrypt(Base64.getDecoder().decode(body), AES_KEY);
		} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
				| BadPaddingException e) {
			e.printStackTrace();
			throw new RuntimeException(e);
		}

		// Construct a new read stream using the decrypted data
		InputStream rawInputStream = new ByteArrayInputStream(body);
		return new HttpInputMessage() {
			@Override
			public HttpHeaders getHeaders(a) {
				return inputMessage.getHeaders();
			}

			@Override
			public InputStream getBody(a) throws IOException {
				returnrawInputStream; }}; }/** * AES decryption **@param data
	 * @param key
	 * @return
	 * @throws InvalidKeyException
	 * @throws NoSuchAlgorithmException
	 * @throws NoSuchPaddingException
	 * @throws IllegalBlockSizeException
	 * @throws BadPaddingException
	 */
	public byte[] decrypt(byte[] data, byte[] key) throws InvalidKeyException, NoSuchAlgorithmException,
			NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
		return cipher.doFinal(data);
	}

	private Cipher getCipher(byte[] key, int model)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException {
		SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
		cipher.init(model, secretKeySpec);
		returncipher; }}Copy the code

TestController

Very simple, almost no changes, just print the client’s request body

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.gson.JsonObject;

@RestController
@RequestMapping("/test")
public class TestController {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(TestController.class);
	
	@PostMapping
	public Object test (@RequestBody JsonObject requestBody) {
		LOGGER.info("requestBody={}", requestBody);
		returnrequestBody; }}Copy the code

The client

Use the same AES key to encrypt the raw data

Gg/hLfWllxXF7KkzeNTPELCZ3jjxgHL24tJWPhO+O7KS5vAS1ag9xmtjP94L8p8BY+HMggCL1mvVEHEL8+FwSSjWYLln8SZk4CuWil2x4sI=
Copy the code

Use Postman to initiate a request

The server responds with the decrypted request body

Server Logs

[xnio-1 task-1] TestController: requestBody={2020-07-22 13:39:31.870 INFO 2684 -- [xnio-1 task-1] TestController: requestBody={"name":"SpringBoot Chinese Community"."site":"https://springboot.io"}

Copy the code

Decrypted the data successfully, and completed the encoding of the JSON object.

Original text: springboot. IO/topic / 226 / t…