Spring Boot interface encryption and decryption, new posture!

Click to follow the official account, Java dry goods will be delivered in time5b2ffd94e1ac4e0b39dae7d9375c98d9.png

82cd4fb16d79f99a2817885aca606a62.png Learn the correct posture of Spring Cloud microservices!

66b43e973547192a46bdf0f9ed86ea4f.png Use ChatGPT, it's ridiculously strong!

3d47f88db79684842257c79a830dd494.png The blog garden is in a desperate situation to survive. .

1 Introduction

In our daily Java development, business interaction with other systems, or interface calls between microservices is inevitable

If we want to ensure the security of data transmission, the outgoing parameters of the interface are encrypted, and the incoming parameters are decrypted.

But don't want to write repetitive code, we can provide a general starter that provides general encryption and decryption functions

2. Prerequisite knowledge

2.1 hutool-crypto encryption and decryption tool

hutool-crypto provides many encryption and decryption tools, including symmetric encryption, asymmetric encryption, digest encryption, etc., which will not be introduced in detail.

2.2 The problem that the request stream can only be read once

2.2.1 Questions:

In the interface call chain, the request stream can only be called once. After processing, if you need to use the request stream to obtain data later, you will find that the data is empty.

For example, if filter or aop is used to obtain the data in the request and verify the parameters before the interface is processed, then the request stream cannot be obtained afterwards. In addition, if you are preparing for an interview to change jobs in the near future, it is recommended to brush up questions online in the Java interview library applet, covering 2000+ Java interview questions, covering almost all mainstream technical interview questions.

2.2.2 Solutions

Inheritance HttpServletRequestWrapper, copy the stream in the request, overwrite getInputStreamand getReader method for external use. The method after each call getInputStreamis obtained from the copied binary array, which exists consistently during the existence of the object.

Using the Filter filter, at the beginning, replace the request with a request defined by yourself that can read the stream multiple times.

In this way, the repeated acquisition of the stream is realized

InputStreamHttpServletRequestWrapper
package xyz.hlh.cryptotest.utils;

import org.apache.commons.io.IOUtils;

import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

/**
 * 请求流支持多次获取
 */
public class InputStreamHttpServletRequestWrapper extends HttpServletRequestWrapper {

    /**
     * 用于缓存输入流
     */
    private ByteArrayOutputStream cachedBytes;

    public InputStreamHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
        if (cachedBytes == null) {
            // 首次获取流时,将流放入 缓存输入流 中
            cacheInputStream();
        }

        // 从 缓存输入流 中获取流并返回
        return new CachedServletInputStream(cachedBytes.toByteArray());
    }

    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }

    /**
     * 首次获取流时,将流放入 缓存输入流 中
     */
    private void cacheInputStream() throws IOException {
        // 缓存输入流以便多次读取。为了方便, 我使用 org.apache.commons IOUtils
        cachedBytes = new ByteArrayOutputStream();
        IOUtils.copy(super.getInputStream(), cachedBytes);
    }

    /**
     * 读取缓存的请求正文的输入流
     * <p>
     * 用于根据 缓存输入流 创建一个可返回的
     */
    public static class CachedServletInputStream extends ServletInputStream {

        private final ByteArrayInputStream input;

        public CachedServletInputStream(byte[] buf) {
            // 从缓存的请求正文创建一个新的输入流
            input = new ByteArrayInputStream(buf);
        }

        @Override
        public boolean isFinished() {
            return false;
        }

        @Override
        public boolean isReady() {
            return false;
        }

        @Override
        public void setReadListener(ReadListener listener) {

        }

        @Override
        public int read() throws IOException {
            return input.read();
        }
    }

}
HttpServletRequestInputStreamFilter
package xyz.hlh.cryptotest.filter;

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import xyz.hlh.cryptotest.utils.InputStreamHttpServletRequestWrapper;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

/**
 * @author HLH
 * @description:
 *      请求流转换为多次读取的请求流 过滤器
 * @email [email protected]
 * @date : Created in 2022/2/4 9:58
 */
@Component
@Order(HIGHEST_PRECEDENCE + 1)  // 优先级最高
public class HttpServletRequestInputStreamFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        // 转换为可以多次获取流的request
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        InputStreamHttpServletRequestWrapper inputStreamHttpServletRequestWrapper = new InputStreamHttpServletRequestWrapper(httpServletRequest);

        // 放行
        chain.doFilter(inputStreamHttpServletRequestWrapper, response);
    }
}

2.3 SpringBoot parameter verification validation

The basics of Spring Boot will not be introduced. It is recommended to watch this free tutorial:

https://github.com/javastacks/spring-boot-best-practice

In order to reduce a large number of redundant parameter verification codes before the business code in the interface

SpringBoot-validationProvides elegant parameter verification. The input parameters are all entity classes. Add corresponding annotations to the entity class fields to perform parameter verification before entering the method. If the parameters are wrong, an error will be thrown and it will not BindExceptionenter method.

This method must require annotations on the interface parameters @Validatedor@Valid

But in many cases, we want to call the verification function of a certain entity class in the code, so the following tool classes are needed.

ParamException
package xyz.hlh.cryptotest.exception;

import lombok.Getter;

import java.util.List;

/**
 * @author HLH
 * @description 自定义参数异常
 * @email [email protected]
 * @date Created in 2021/8/10 下午10:56
 */
@Getter
public class ParamException extends Exception {

    private final List<String> fieldList;
    private final List<String> msgList;

    public ParamException(List<String> fieldList, List<String> msgList) {
        this.fieldList = fieldList;
        this.msgList = msgList;
    }
}
ValidationUtils
package xyz.hlh.cryptotest.utils;

import xyz.hlh.cryptotest.exception.CustomizeException;
import xyz.hlh.cryptotest.exception.ParamException;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

/**
 * @author HLH
 * @description 验证工具类
 * @email [email protected]
 * @date Created in 2021/8/10 下午10:56
 */
public class ValidationUtils {

    private static final Validator VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();

    /**
     * 验证数据
     * @param object 数据
     */
    public static void validate(Object object) throws CustomizeException {

        Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object);

        // 验证结果异常
        throwParamException(validate);
    }

    /**
     * 验证数据(分组)
     * @param object 数据
     * @param groups 所在组
     */
    public static void validate(Object object, Class<?> ... groups) throws CustomizeException {

        Set<ConstraintViolation<Object>> validate = VALIDATOR.validate(object, groups);

        // 验证结果异常
        throwParamException(validate);
    }

    /**
     * 验证数据中的某个字段(分组)
     * @param object 数据
     * @param propertyName 字段名称
     */
    public static void validate(Object object, String propertyName) throws CustomizeException {
        Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName);

        // 验证结果异常
        throwParamException(validate);

    }

    /**
     * 验证数据中的某个字段(分组)
     * @param object 数据
     * @param propertyName 字段名称
     * @param groups 所在组
     */
    public static void validate(Object object, String propertyName, Class<?> ... groups) throws CustomizeException {

        Set<ConstraintViolation<Object>> validate = VALIDATOR.validateProperty(object, propertyName, groups);

        // 验证结果异常
        throwParamException(validate);

    }

    /**
     * 验证结果异常
     * @param validate 验证结果
     */
    private static void throwParamException(Set<ConstraintViolation<Object>> validate) throws CustomizeException {
        if (validate.size() > 0) {
            List<String> fieldList = new LinkedList<>();
            List<String> msgList = new LinkedList<>();
            for (ConstraintViolation<Object> next : validate) {
                fieldList.add(next.getPropertyPath().toString());
                msgList.add(next.getMessage());
            }

            throw new ParamException(fieldList, msgList);
        }
    }

}

2.5 Custom starter

Custom starter steps

  • Create factory, write function code

  • Declare the automatic configuration class, create the objects that need to be provided externally, and expose them uniformly through the configuration class

  • spring/spring.factoriesPrepare a file org.springframework.boot.autoconfigure.EnableAutoConfigurationnamed in the resource directory as the key, and automatically configure the class as the value list for registration

2.6 RequestBodyAdvice和ResponseBodyAdvice

  • RequestBodyAdviceIt is to process the requested json string, and the general use environment is to process the automatic decryption of interface parameters

  • ResponseBodyAdviceIt is to process the corresponding json transmission of the request, and is generally used for the encryption of the corresponding result

3. Function Introduction

When the interface corresponds to data, the encrypted data is returned. When the interface is entered as a parameter, the decrypted data is received, but before entering the interface, it will be automatically decrypted to obtain the corresponding data

4. Function Details

Encryption and decryption use the AES algorithm of symmetric encryption, and use the hutool-crypto module to implement

All entity classes extract a common parent class, including the attribute timestamp, which is used to encrypt the validity of the data after it is returned. If it exceeds 60 minutes, other interfaces will not be processed.

If the interface is annotated with encryption EncryptionAnnotationand returns a unified json data Result class, the data will be automatically encrypted. If the data inherits the unified parent class RequestBase, the time stamp is automatically injected to ensure the timeliness of the data

If the interface has a decryption annotation DecryptionAnnotation, and the parameters are marked with the RequestBody annotation, the incoming json uses the unified format RequestData class, and the content inherits the parent class with a long time, it will be RequestBaseautomatically decrypted and converted to the corresponding data type

The function provides the starter of Springboot, which can be used out of the box

5. Code implementation

https://gitee.com/springboot-hlh/spring-boot-csdn/tree/master/09-spring-boot-interface-crypto

5.1 Project structure

f8b983011b012384215590e84ada7f4f.png

5.2 crypto-common

5.2.1 Structure
f8f153d9b09f075bf3e148f02843da4e.png
5.3 crypto-spring-boot-starter
5.3.1 Interface
8af6bcda93744c493a122c8c2b924147.png
5.3.2 Important codes

The parameter configuration required by crypto.properties AES

# 模式    cn.hutool.crypto.Mode
crypto.mode=CTS
# 补码方式 cn.hutool.crypto.Mode
crypto.padding=PKCS5Padding
# 秘钥
crypto.key=testkey123456789
# 盐
crypto.iv=testiv1234567890

spring.factories automatic configuration file

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
xyz.hlh.crypto.config.AppConfig

Configuration parameters required by CryptConfig AES

package xyz.hlh.crypto.config;

import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.Serializable;

/**
 * @author HLH
 * @description: AES需要的配置参数
 * @email [email protected]
 * @date : Created in 2022/2/4 13:16
 */
@Configuration
@ConfigurationProperties(prefix = "crypto")
@PropertySource("classpath:crypto.properties")
@Data
@EqualsAndHashCode
@Getter
public class CryptConfig implements Serializable {

    private Mode mode;
    private Padding padding;
    private String key;
    private String iv;

}

AppConfig automatic configuration class

package xyz.hlh.crypto.config;

import cn.hutool.crypto.symmetric.AES;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;

/**
 * @author HLH
 * @description: 自动配置类
 * @email [email protected]
 * @date : Created in 2022/2/4 13:12
 */
@Configuration
public class AppConfig {

    @Resource
    private CryptConfig cryptConfig;

    @Bean
    public AES aes() {
        return new AES(cryptConfig.getMode(), cryptConfig.getPadding(), cryptConfig.getKey().getBytes(StandardCharsets.UTF_8), cryptConfig.getIv().getBytes(StandardCharsets.UTF_8));
    }

}

DecryptRequestBodyAdviceRequest automatic decryption, click to follow the official account, Java dry goods will be delivered in timeb7dd0ac5ed20f73cb9d689a45e1ad86e.png

package xyz.hlh.crypto.advice;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
import xyz.hlh.crypto.annotation.DecryptionAnnotation;
import xyz.hlh.crypto.common.exception.ParamException;
import xyz.hlh.crypto.constant.CryptoConstant;
import xyz.hlh.crypto.entity.RequestBase;
import xyz.hlh.crypto.entity.RequestData;
import xyz.hlh.crypto.util.AESUtil;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.lang.reflect.Type;

/**
 * @author HLH
 * @description: requestBody 自动解密
 * @email [email protected]
 * @date : Created in 2022/2/4 15:12
 */
@ControllerAdvice
public class DecryptRequestBodyAdvice implements RequestBodyAdvice {

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 方法上有DecryptionAnnotation注解的,进入此拦截器
     * @param methodParameter 方法参数对象
     * @param targetType 参数的类型
     * @param converterType 消息转换器
     * @return true,进入,false,跳过
     */
    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        return methodParameter.hasMethodAnnotation(DecryptionAnnotation.class);
    }

    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {
        return inputMessage;
    }

    /**
     * 转换之后,执行此方法,解密,赋值
     * @param body spring解析完的参数
     * @param inputMessage 输入参数
     * @param parameter 参数对象
     * @param targetType 参数类型
     * @param converterType 消息转换类型
     * @return 真实的参数
     */
    @SneakyThrows
    @Override
    public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {

        // 获取request
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;
        if (servletRequestAttributes == null) {
            throw new ParamException("request错误");
        }

        HttpServletRequest request = servletRequestAttributes.getRequest();

        // 获取数据
        ServletInputStream inputStream = request.getInputStream();
        RequestData requestData = objectMapper.readValue(inputStream, RequestData.class);

        if (requestData == null || StringUtils.isBlank(requestData.getText())) {
            throw new ParamException("参数错误");
        }

        // 获取加密的数据
        String text = requestData.getText();

        // 放入解密之前的数据
        request.setAttribute(CryptoConstant.INPUT_ORIGINAL_DATA, text);

        // 解密
        String decryptText = null;
        try {
            decryptText = AESUtil.decrypt(text);
        } catch (Exception e) {
            throw new ParamException("解密失败");
        }

        if (StringUtils.isBlank(decryptText)) {
            throw new ParamException("解密失败");
        }

        // 放入解密之后的数据
        request.setAttribute(CryptoConstant.INPUT_DECRYPT_DATA, decryptText);

        // 获取结果
        Object result = objectMapper.readValue(decryptText, body.getClass());

        // 强制所有实体类必须继承RequestBase类,设置时间戳
        if (result instanceof RequestBase) {
            // 获取时间戳
            Long currentTimeMillis = ((RequestBase) result).getCurrentTimeMillis();
            // 有效期 60秒
            long effective = 60*1000;

            // 时间差
            long expire = System.currentTimeMillis() - currentTimeMillis;

            // 是否在有效期内
            if (Math.abs(expire) > effective) {
                throw new ParamException("时间戳不合法");
            }

            // 返回解密之后的数据
            return result;
        } else {
            throw new ParamException(String.format("请求参数类型:%s 未继承:%s", result.getClass().getName(), RequestBase.class.getName()));
        }
    }

    /**
     * 如果body为空,转为空对象
     * @param body spring解析完的参数
     * @param inputMessage 输入参数
     * @param parameter 参数对象
     * @param targetType 参数类型
     * @param converterType 消息转换类型
     * @return 真实的参数
     */
    @SneakyThrows
    @Override
    public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        String typeName = targetType.getTypeName();
        Class<?> bodyClass = Class.forName(typeName);
        return bodyClass.newInstance();
    }
}

EncryptResponseBodyAdviceAutomatically encrypt accordingly

package xyz.hlh.crypto.advice;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;
import xyz.hlh.crypto.annotation.EncryptionAnnotation;
import xyz.hlh.crypto.common.entity.Result;
import xyz.hlh.crypto.common.exception.CryptoException;
import xyz.hlh.crypto.entity.RequestBase;
import xyz.hlh.crypto.util.AESUtil;

import java.lang.reflect.Type;

/**
 * @author HLH
 * @description:
 * @email [email protected]
 * @date : Created in 2022/2/4 15:12
 */
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Result<?>> {

    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        ParameterizedTypeImpl genericParameterType = (ParameterizedTypeImpl)returnType.getGenericParameterType();

        // 如果直接是Result,则返回
        if (genericParameterType.getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {
            return true;
        }

        if (genericParameterType.getRawType() != ResponseEntity.class) {
            return false;
        }

        // 如果是ResponseEntity<Result>
        for (Type type : genericParameterType.getActualTypeArguments()) {
            if (((ParameterizedTypeImpl) type).getRawType() == Result.class && returnType.hasMethodAnnotation(EncryptionAnnotation.class)) {
                return true;
            }
        }

        return false;
    }

    @SneakyThrows
    @Override
    public Result<?> beforeBodyWrite(Result<?> body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {

        // 加密
        Object data = body.getData();

        // 如果data为空,直接返回
        if (data == null) {
            return body;
        }

        // 如果是实体,并且继承了Request,则放入时间戳
        if (data instanceof RequestBase) {
            ((RequestBase)data).setCurrentTimeMillis(System.currentTimeMillis());
        }

        String dataText = JSONUtil.toJsonStr(data);

        // 如果data为空,直接返回
        if (StringUtils.isBlank(dataText)) {
            return body;
        }

        // 如果位数小于16,报错
        if (dataText.length() < 16) {
            throw new CryptoException("加密失败,数据小于16位");
        }

        String encryptText = AESUtil.encryptHex(dataText);

        return Result.builder()
                .status(body.getStatus())
                .data(encryptText)
                .message(body.getMessage())
                .build();
    }
}
5.4 crypto-test
5.4.1 Structure

72f2c5bb3702d28cece81bb7d11e3eb2.png

5.4.2 Important codes

application.yml configuration file

spring:
  mvc:
    format:
      date-time: yyyy-MM-dd HH:mm:ss
      date: yyyy-MM-dd
  # 日期格式化
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

Teacher entity class

package xyz.hlh.crypto.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Date;

/**
 * @author HLH
 * @description: Teacher实体类,使用SpringBoot的validation校验
 * @email [email protected]
 * @date : Created in 2022/2/4 10:21
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class Teacher extends RequestBase implements Serializable {

    @NotBlank(message = "姓名不能为空")
    private String name;
    @NotNull(message = "年龄不能为空")
    @Range(min = 0, max = 150, message = "年龄不合法")
    private Integer age;
    @NotNull(message = "生日不能为空")
    private Date birthday;

}

TestController Test Controller

package xyz.hlh.crypto.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import xyz.hlh.crypto.annotation.DecryptionAnnotation;
import xyz.hlh.crypto.annotation.EncryptionAnnotation;
import xyz.hlh.crypto.common.entity.Result;
import xyz.hlh.crypto.common.entity.ResultBuilder;
import xyz.hlh.crypto.entity.Teacher;

/**
 * @author HLH
 * @description: 测试Controller
 * @email [email protected]
 * @date : Created in 2022/2/4 9:16
 */
@RestController
public class TestController implements ResultBuilder {

    /**
     * 直接返回对象,不加密
     * @param teacher Teacher对象
     * @return 不加密的对象
     */
    @PostMapping("/get")
    public ResponseEntity<Result<?>> get(@Validated @RequestBody Teacher teacher) {
        return success(teacher);
    }

    /**
     * 返回加密后的数据
     * @param teacher Teacher对象
     * @return 返回加密后的数据 ResponseBody<Result>格式
     */
    @PostMapping("/encrypt")
    @EncryptionAnnotation
    public ResponseEntity<Result<?>> encrypt(@Validated @RequestBody Teacher teacher) {
        return success(teacher);
    }

    /**
     * 返回加密后的数据
     * @param teacher Teacher对象
     * @return 返回加密后的数据 Result格式
     */
    @PostMapping("/encrypt1")
    @EncryptionAnnotation
    public Result<?> encrypt1(@Validated @RequestBody Teacher teacher) {
        return success(teacher).getBody();
    }

    /**
     * 返回解密后的数据
     * @param teacher Teacher对象
     * @return 返回解密后的数据
     */
    @PostMapping("/decrypt")
    @DecryptionAnnotation
    public ResponseEntity<Result<?>> decrypt(@Validated @RequestBody Teacher teacher) {
        return success(teacher);
    }

}

Copyright statement: This article is an original article of CSDN blogger "HLH_2021", following the CC 4.0 BY-SA copyright agreement, please attach the original source link and this statement for reprinting. Original link: https://blog.csdn.net/HLH_2021/article/details/122785888


Finally, I promoted my knowledge planet, and people in the circle of friends often asked me questions, but they couldn't answer them at all, so Brother R created a "Java Technology Small Circle" knowledge planet, and 2500+ friends joined.

3e6e317be9fba3c484f337b686bb6f07.pngClick the " Java Technology Small Circle " link to learn more.

The value is tens of thousands +, quickly scan the code to join:

5c165333977985e733ea81f76e067a86.png

The price is about to increase, it has long been an advantage!

End

f0eaccc400bc6eafa35eb3aa45a79c28.png Learn the correct posture of Spring Cloud microservices!

0e31296b56d1c389ea69116d93e2dbbb.png The blog garden is in a desperate situation to survive. .

481d749f3b5cc61ff9f108c9f33df114.png Use ChatGPT, it's ridiculously strong!

b3cf51860b3eb1756d325234544fa7de.png How to delete duplicate elements in HashMap?

398e0dff979867b9c0b26de7b62f3e2a.png Java 20 is officially released, and it's amazing. .

d407d6611f07bb52ab110f4a3323ab7a.gif

The latest course on Spring Cloud microservices!

Guess you like

Origin blog.csdn.net/youanyyou/article/details/130143323