Because some small partners just asked this question, Songge took time out to write an article to talk about this topic with you.

Encryption and decryption itself is not difficult, the question is when to deal with? Another option is to define a filter that intercepts the request and response separately for processing. This is crude, but flexible, because you can get the request parameters and response data in one hand. SpringMVC, however, provides responseBodyAdvice and RequestBodyAdvice, which are handy tools for preprocessing requests and responses.

So today’s post has two purposes:

  • Share ideas on encryption and decryption of parameters/responses.
  • Share the use of responseBodyAdvice and requestBodyAdvice.

All right, so let’s cut the crap, let’s take a look.

1. Develop encryption and decryption starter

To make the tool we developed more versatile and to review custom Spring Boot Starter, we will make the tool a stater that we can refer to directly in the Spring Boot project.

First we create a Spring Boot project and introduce the spring-boot-starter-web dependency:

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> The < scope > provided < / scope > < version > 2.4.3 < / version > < / dependency >

The scope is set to Provided because we are developing this tool for Web projects and will use it in the future.

After the dependency is added, we will define a encryption tool class for backup. There are many options for encryption, such as symmetric encryption and asymmetric encryption. In symmetric encryption, different algorithms such as AES, DES, 3DES can be used. Using the AES algorithm:

public class AESUtils { private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; Private static Cipher getCipher(byte[] key); int model) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(model, secretKeySpec); return cipher; } public static String Encrypt (byte[] data, byte[] key) throws Exception {Cipher Cipher = getCipher(key, Cipher); Cipher.ENCRYPT_MODE); return Base64.getEncoder().encodeToString(cipher.doFinal(data)); } public static Byte [] Decrypt (Byte [] Data, Byte [] Key) throws Exception {Cipher Cipher = GetCipher (Key, Byte [] Key); Cipher.DECRYPT_MODE); return cipher.doFinal(Base64.getDecoder().decode(data)); }}

This utility class is relatively simple and does not need much explanation. It should be noted that the encrypted data may not be readable, so we generally need to encode the encrypted data using Base64 algorithm to obtain the readable string. In other words, the return value of the AES encryption method above is a Base64-encoded string, and the argument to the AES decryption method is also a Base64-encoded string, which is decoded and then decrypted.

Next, we’ll wrap a response tool class as a backup, which is already familiar if you’ve watched Songo’s videos frequently:

public class RespBean { private Integer status; private String msg; private Object obj; public static RespBean build() { return new RespBean(); } public static RespBean ok(String msg) { return new RespBean(200, msg, null); } public static RespBean ok(String msg, Object obj) { return new RespBean(200, msg, obj); } public static RespBean error(String msg) { return new RespBean(500, msg, null); } public static RespBean error(String msg, Object obj) { return new RespBean(500, msg, obj); } private RespBean() { } private RespBean(Integer status, String msg, Object obj) { this.status = status; this.msg = msg; this.obj = obj; } public Integer getStatus() { return status; } public RespBean setStatus(Integer status) { this.status = status; return this; } public String getMsg() { return msg; } public RespBean setMsg(String msg) { this.msg = msg; return this; } public Object getObj() { return obj; } public RespBean setObj(Object obj) { this.obj = obj; return this; }}

Next we define two annotations @decrypt and @encrypt:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}

These annotations are two tokens that will be used in the future to Encrypt data from the interface method with the @Encrypt annotation and Decrypt data from the interface/parameter with the @Decrypt annotation. This definition is also fairly simple, so there’s not much to say, but it’s important to note that @decrypt has one more use case than @encrypt where you can use @decrypt for parameters.

Given that the user may configure the encrypted key itself, we define an EncryptProperties class to read the user-configured key:

@ConfigurationProperties(prefix = "spring.encrypt") public class EncryptProperties { private final static String DEFAULT_KEY = "www.itboyhub.com"; private String key = DEFAULT_KEY; public String getKey() { return key; } public void setKey(String key) { this.key = key; }}

Here I set the default key to www.itboyhub.com, the key is a 16-bit string, songo this website address is just right. In the future, if users want to configure their own keys, they can simply configure spring.encrypt.key= XXX in application.properties.

After all the preparation work is done, it is time to officially encrypt and decrypt.

Since an important purpose of Songo’s post is to share the use of responseBodyAdvice and requestBodyAdvice, requestBodyAdvice has no problem decrypting it, The responseBodyAdvice has some limitations when it comes to encrypting, but it’s not a big deal. As I said before, if you want to be very flexible with everything, you should use a custom filter. So I’m going to start with these two tools.

Also note that the responseBodyAdvice will only take effect if you use the @ResponseBody annotation. RequestBodyAdvice will only take effect if you use the @RequestBody annotation. In other words, These two are useful when both the front and back ends interact with JSON. But generally speaking, the scene of interface encryption and decryption is also possible when the front and back ends are separated.

Let’s start with interface encryption:

@EnableConfigurationProperties(EncryptProperties.class) @ControllerAdvice public class EncryptResponse implements ResponseBodyAdvice<RespBean> { private ObjectMapper om = new ObjectMapper(); @Autowired EncryptProperties encryptProperties; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<? >> converterType) { return returnType.hasMethodAnnotation(Encrypt.class); } @Override public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<? >> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { byte[] keyBytes = encryptProperties.getKey().getBytes(); try { if (body.getMsg()! =null) { body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes)); } if (body.getObj() ! = null) { body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes)); } } catch (Exception e) { e.printStackTrace(); } return body; }}

We’ve customized the EncryptResponse class to implement the responseBodyAdvice < respBean > interface. Generics represent the return type of the interface. There are two methods to implement here:

  1. Supports: This method is used to determine what interface needs to be encrypted. The returnType parameter indicates the returnType. Our logic here is whether the method contains or not@EncryptAnnotations, if there is, indicate that the interface requires encryption; if not, indicate that the interface does not require encryption.
  2. BeforeBodyWrite: This method is executed before the data response, meaning that we process the response data twice before it is returned as JSON. Our approach is simple, the status of the RespBean is a status code need not encryption, the other two fields to encrypted reset value.
  3. Also note that a custom responseBodyAdvice is required@ControllerAdviceComments to mark.

Let’s look at interface decryption:

@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
public class DecryptRequest extends RequestBodyAdviceAdapter {
    @Autowired
    EncryptProperties encryptProperties;
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        byte[] body = new byte[inputMessage.getBody().available()];
        inputMessage.getBody().read(body);
        try {
            byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes());
            final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt);
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return bais;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}
  1. First of all, notice that the DecryptRequest class is not implemented directlyRequestBodyAdviceInterface, but inherits from the RequestBodyAdviceAdapter class, which is a subclass of the RequestBodyAdviceAdapter and implements some of the methods in the interface, so that when we inherit from the RequestBodyAdviceAdapter, You just need to implement a few methods according to your actual needs.
  2. Supports: This method is used to determine which interfaces need to handle interface decryption, and our logic here is on methods or parameters@DecryptThe annotation interface handles decryption issues.
  3. BeforeBodyRead: This method is executed before the parameters are converted to concrete objects by loading the data from the stream, decrypting the data, and then re-structuring the HttpInputMessage object to return.

Next, let’s define an automation configuration class as follows:

@Configuration
@ComponentScan("org.javaboy.encrypt.starter")
public class EncryptAutoConfiguration {

}

There’s nothing to say about this, it’s easy.

Finally, define META-INF under the resources directory, and then define the spring.factories file as follows:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.javaboy.encrypt.starter.autoconfig.EncryptAutoConfigu ration

This will automatically load the configuration class when the project starts.

At this point, the development of our starter is complete.

2. Package and publish

We can install the project to a local repository or publish it online for others to use.

2.1 Install to the local warehouse

It is easy to install to the local repository, directly MVN Install, or in IDEA, click Maven on the right, and then double-click Install, as follows:

2.2 Publish online

We can use Jitpack to do it.

First we created a repository on GitHub and uploaded our code, which I don’t need to tell you about.

After uploading successfully, click the Create a New Release button on the right to release an official version, as follows:

After successful release, open the Jitpack, enter the full path of the repository, click Lookup button, find it, and then click Get It button to complete the build, as follows:

After a successful build, the project reference will be given on the Jitpack:

Be sure to change the tag to your version number when quoting.

At this point, our tool has been successfully released! Friends can refer to the starter in the following ways:

<dependencies> <dependency> <groupId>com.github.lenve</groupId> <artifactId>encrypt-spring-boot-starter</artifactId> </version> 0.0.3</version> </ Dependencies > < Repositories > < ID >jitpack. IO </ ID > <url>https://jitpack.io</url> </repository> </repositories>

3. The application of

Let’s create a normal Spring Boot project, introduce the web dependency, and then the starter dependency we just introduced, as follows:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId>  </dependency> <dependency> <groupId>com.github.lenve</groupId> <artifactId>encrypt-spring-boot-starter</artifactId> < version > 0.0.3 < / version > < / dependency > < the dependency > < groupId > org. Springframework. Boot < / groupId > <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>

Then create an entity class for backup:

public class User { private Long id; private String username; // Omit getter/setter}

Create two test interfaces:

@RestController public class HelloController { @GetMapping("/user") @Encrypt public RespBean getUser() { User user = new  User(); user.setId((long) 99); user.setUsername("javaboy"); return RespBean.ok("ok", user); } @PostMapping("/user") public RespBean addUser(@RequestBody @Decrypt User user) { System.out.println("user = " + user);  return RespBean.ok("ok", user); }}

The first interface uses the @Encrypt annotation, so its data is encrypted (and otherwise not). The second interface uses the @Decrypt annotation, so the uploaded parameters are decrypted. Note that the @Decrypt annotation can be placed on either methods or parameters.

Next, launch the project for testing.

First test the GET request interface:

As you can see, the returned data is encrypted.

Let’s test the POST request again:

As you can see, the encrypted data in the parameter has been restored.

If the user wants to change the encryption key, add the following configuration to Application.properties:

spring.encrypt.key=1234567890123456

Encryption data to the front end, the front end also has some JS tools to deal with encryption data, this songge behind free and then talk to you about JS encryption and decryption.

4. Summary

OK, so the purpose of today’s article is to talk about the use of responseBodyAdvice and requestBodyAdvice, some encryption ideas, The responseBodyAdvice and requestBodyAdvice are used in a number of other scenarios that you can explore on your own. This article uses the AES algorithm in symmetric encryption. You can also try asymmetric encryption instead.

Well, today is so much to talk about, friends can go to try ~ public number background reply 20210309 can download the case of this article ~