springboot接口入参出参实现加密解密

主要使用自定义注解结合springmvc里的RequestBodyAdvice和ResponseBodyAdvice两个类进行实现。

RequestBodyAdvice允许针对接口请求体被读取之前进行修改,ResponseBodyAdvice允许接口出参在被返回之前进行修改。

  1. 新建两个自定义注解类,用来标记接口需要进行加密解密。

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
 * 功能描述
 * @Encrypt配置的作用域是在方法上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * 功能描述
 * @Decrypt配置的作用域是方法和参数上
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}
  1. 新建AESUtils工具类

import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

/**
 * AES加解密工具类
 */
@Component
public class AESUtils {
    private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding";

    // 获取 cipher
    private 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;
    }

    // AES加密
    public String encrypt(byte[] data, byte[] key) throws Exception {
        Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE);
        return Base64.getEncoder().encodeToString(cipher.doFinal(data));
    }

    // AES解密
    public byte[] decrypt(byte[] data, byte[] key) throws Exception {
        Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE);
        return cipher.doFinal(Base64.getDecoder().decode(data));
    }
}
  1. 新建自定义DecryptRequestAdvice类继承RequestBodyAdviceAdapter,进行入参解密

import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdviceAdapter;

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

/**
 * 功能描述 encryptKey是配置在yml文件中的加密解密key(该key要为16字节或者是16字节的n次方,要不然报错)
 */
@ControllerAdvice
@Slf4j
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {

    @Value("${spring.encrypt.key}")
    private String encryptKey;

    @Autowired
    private AESUtils aesUtil;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class> converterType) {
        // 当某个接口方法或参数上加了@Decrypt注解,才进行解密操作
        return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class);
    }

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

        InputStream inputStream = inputMessage.getBody();

        byte[] bytes = new byte[inputStream.available()];
        inputStream.read(bytes);
        String requestBody = new String(bytes);

        try {
            // 结合自己业务针对获取到的requestBody内容进行修改解密等操作
            String decryptData = aesUtil.decrypt(requestBody.getBytes() , encryptKey.getBytes()).toString();// 把入参修改为解密后的内容
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptData.getBytes());
            return new HttpInputMessage() {
                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
                @Override
                public InputStream getBody() throws IOException {
                    return byteArrayInputStream;
                }


            };
        } catch (Exception e) {
            log.error("接口入参解密出错:{}", Throwables.getStackTraceAsString(e));
        }

        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}
  1. 新建自定义EncryptResponseAdvice类继承ResponseBodyAdvice,进行出参加密

这里的Result是自定义的接口返回封装类

import cn.hutool.json.JSONUtil;
import com.google.common.base.Throwables;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 功能描述  encryptKey是配置在yml文件中的加密解密key(该key要为16字节或者是16字节的n次方,要不然报错)
 */
@ControllerAdvice
@Slf4j
public class EncryptResponseAdvice implements ResponseBodyAdvice {


    @Value("${spring.encrypt.key}")
    private String encryptKey;

    @Autowired
    private AESUtils aesUtil;


    @Override
    public boolean supports(MethodParameter returnType, Class> converterType) {
        // 只有接口上加了@Encrypt注解才进行出参加密操作
        return returnType.hasMethodAnnotation(Encrypt.class);
    }

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

        try {
            if (body.getResult() != null) {
                log.info("接口加密前返回的数据:{}", JSONUtil.toJsonStr(body.getResult()));
                String encStr = aesUtil.encrypt(JSONUtil.toJsonStr(body.getResult()).getBytes(), encryptKey.getBytes());

                log.info("接口加密后返回的数据:{}", encStr);
                body.setResult(encStr);
            }
        } catch (Exception e) {
            log.error("接口返回数据加密出错:{}", Throwables.getStackTraceAsString(e));
        }
        return body;
    }
}

6. controller接口

    @PostMapping("/test")
    @ApiOperation(value = "测试接口加密解密")
    @Encrypt
    public Result test(@Decrypt @RequestBody @Valid QueryVO vo) {
        UserInfoDTO convert = Convert.convert(UserInfoDTO.class, vo);return new R(convert);
    }

上面那两个advice类型,都要使用@ControllerAdvice注解进行修饰,它其实是一个实现特殊功能的@Component,只是针对controller进行拦截,本质还是aop,我们平常使用的全局异常处理类@ExceptionHandler,GlobalExceptionHandler,也是配合该注解使用。

注意:另外这边入参拦截修改,只针对@RequestBody修饰的body进行处理,同时返回一样,要被@ResponseBody修饰,如果你使用的是@RestController,就不需要加了。

你可能感兴趣的:(Java工具类,spring,boot,spring,java)