How to implement the encryption and decryption function of interface data flexibly in spring boot?

Keywords: Java Spring Fragment SpringBoot

Data is the fourth business card of an enterprise. Encrypted data transmission is indispensable in enterprise development. Therefore, this paper introduces the methods of interface data encryption and decryption in SpringBoot.

Contents of this article

I. Introduction to encryption schemeII. Implementation principleThree, actual combatFour, test5. The pit

I. Introduction to encryption scheme

There are two main ways to encrypt and decrypt the interface:

  1. Custom message converter

Advantages: it only needs to implement the interface and simple configuration.
Disadvantage: it can only encrypt and decrypt the same type of MediaType, which is not flexible.

  1. Use the interface provided by spring RequestBodyAdvice and ResponseBodyAdvice

Advantage: it can be judged according to the Referrer, Header or url of the request, and encrypted and decrypted according to the specific needs.

For example, when upgrading a project, the interface of the new development function needs to be encrypted and decrypted, and the logic before the old function module goes is not encrypted. At this time, only the second method above can be selected. The next mainly introduces the process of encryption and decryption in the second method.

II. Implementation principle

RequestBodyAdvice can be understood as the operation to be performed before @ RequestBody, and ResponseBodyAdvice can be understood as the operation to be performed after @ ResponseBody. Therefore, when the interface needs to be encrypted and decrypted, the parameter can be decrypted in the implementation class of RequestBodyAdvice before using @ RequestBody to receive the foreground parameters. When the operation ends and needs to return data, the parameter can be decrypted in the implementation class of RequestBodyAdvice.@ ResponseBody then enters the implementation class of ResponseBodyAdvice to encrypt the parameters.

RequestBodyAdvice process:

The source code of RequestBodyAdvice is as follows:

 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);


}

Part of the code for calling the RequestBodyAdvice implementation class is as follows:

 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;
    }

From the above source code, when converter.canRead() and message.hasBody() are both true, the beforeBodyRead() and afterBodyRead() methods will be called, so we can add the decryption code in the afterBodyRead() of the implementation class.

ResponseBodyAdvice processes the response:

The source code of ResponseBodyAdvice is as follows:

public interface ResponseBodyAdvice<T{


    boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType);


    @Nullable
    beforeBodyWrite(@Nullable T body, MethodParameter returnType, MediaType selectedContentType,
            Class<? extends HttpMessageConverter<?>> selectedConverterType,
            ServerHttpRequest request, ServerHttpResponse response)
;

}

Part of the code for calling the ResponseBodyAdvice implementation class is as follows:

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;
                }
            }
        }

From the above source code, when converter.canWrite() is true, the beforeBodyWrite() method will be called, so we can add decryption code in the beforeBodyWrite() of the implementation class.

Three, actual combat

Create a new spring boot project, spring boot encryption, and follow the steps below.

  1. Introducing jar into pom.xml
  <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>
  1. Request parameter decryption interception class

The DecryptRequestBodyAdvice code is as follows:

/**
 * Request parameter decryption operation
 *
 * @Author: Java Fragment
 * @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 {
            //Decryption operation
            Map<String,String> dataMap = (Map)body;
            String srcData = dataMap.get("data");
            dealData = DesUtil.decrypt(srcData);
        } catch (Exception e) {
            log.error("Abnormal!", 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;
    }


}
  1. Response parameter encryption interception class

The EncryResponseBodyAdvice code is as follows:

/**
 * Request parameter decryption operation
 *
 * @Author: Java Fragment
 * @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<?>> selectedConverterTypeServerHttpRequest serverHttpRequest,
                                  ServerHttpResponse serverHttpResponse
{
        //Get HttpServletRequest through the implementation class ServletServerHttpRequest of ServerHttpRequest
        ServletServerHttpRequest sshr = (ServletServerHttpRequest) serverHttpRequest;
        //The purpose of getting request here is to get an object set in the interceptor. It is required by my project and can be ignored.
        HttpServletRequest request = sshr.getServletRequest();

        String returnStr = "";

        try {
            //Add an encryption header to tell the front-end that the data is encrypted
            serverHttpResponse.getHeaders().add("encry""true");
            String srcData = JSON.toJSONString(obj);
            //encryption
            returnStr = DesUtil.encrypt(srcData);
            log.info("Interface={},Raw data={},Encrypted data={}", request.getRequestURI(), srcData, returnStr);

        } catch (Exception e) {
            log.error("Abnormal!", e);
        }
        return returnStr;
    }
  1. New controller class

The TestController code is as follows:

/**
 * @Author: Java Fragment
 * @Date: 2019/10/24 21:40
 */

@RestController
public class TestController {

    Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Response data encryption
     */

    @RequestMapping(value = "/sendResponseEncryData")
    public Result sendResponseEncryData() {
        Result result = Result.createResult().setSuccess(true);
        result.setDataValue("name""Java Fragment");
        result.setDataValue("encry"true);
        return result;
    }

    /**
     * Get request parameters after decryption
     */

    @RequestMapping(value = "/getRequestData")
    public Result getRequestData(@RequestBody Object object) {
        log.info("controller Parameters received object={}"object.toString());
        Result result = Result.createResult().setSuccess(true);
        return result;
    }
}
  1. Other classes are in the source code, followed by the github address

Four, test

  1. Access response data encryption interface

Use postman to send a request to http://localhost:8888/sendResponseEncryData. You can see that the returned data has been encrypted. The screenshot of the request is as follows:


Response data encryption screenshot

Related logs are also printed in the background, as follows:

Interface = / sendResponseEncryData

Raw data = {"data": {"entry": true, "name": "Java fragment"}, "success":true}

Data after encryption = vjc26g3sqru9gajdg7rhnax6ky / ihgioagdwi6almtynab4nenebmxvdskepnia5bqat7zaimal7
3VeicCuSTA==
  1. Access request data decryption interface

Using postman to send the request http://localhost:8888/getRequestData, you can see that the request data has been decrypted. The screenshot of the request is as follows:


Request data decryption screenshot

Related logs are also printed in the background, as follows:

Received original request data = {"data": "vwlvde8n6fusxn / jrrjavatopa3m1qen + 9bkuf2jpwc1esofgahq ="}

Decrypted data = {"name":"Java fragment", "des": "request parameter"}

5. The pit

  1. When testing the decryption request parameters, the request body must have data, otherwise the implementation class will not be called to trigger the decryption operation.

How to flexibly implement the encryption and decryption function of interface data in this spring boot has been fully implemented. If you have any questions, please leave a message and communicate!

Full source address: https://github.com/suisuisui2019/springboot-study

Click "read the original" at the bottom of the article to go to the source address.

Recommended reading

1. The magic @ Enable * annotation in springboot?
2. In Java, Integer.parseInt and Integer.valueOf, are you still confused?
3. Spring cloud series - two ways to integrate Hystrix
4. Spring cloud series - Implementing declarative service invocation with Feign
5. Use the Ribbon to balance the load of the client

Collect free Java related materials in limited time, covering Java, Redis, MongoDB, MySQL, Zookeeper, Spring Cloud, Dubbo/Kafka, Hadoop, Hbase, Flink and other high concurrent distributed, big data, machine learning and other technologies.
Pay attention to the following public ID and get it free of charge:

Java fragment public account

 

Posted by ankur0101 on Sun, 27 Oct 2019 19:47:34 -0700