天天看點

springboot修改接口入參出參實作入參加密出參解密

一、背景

針對項目已經開發完的接口,都需要加上傳輸資料加密的功能,對接口入參進行AES解密,對接口出參進行加密。考慮到盡量改動少點,使用自定義注解結合springmvc裡的RequestBodyAdvice和ResponseBodyAdvice兩個類進行實作。

RequestBodyAdvice允許針對接口請求體被讀取之前進行修改,ResponseBodyAdvice允許接口出參在被傳回之前進行修改。

二、實作

1、建立兩個自定義注解類,用來标記哪些接口需要進行加密解密。

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

注意:@Decrypt配置的作用域是方法和參數上,@Encrypt則是隻在方法上。

2、建立自定義DecryptRequestAdvice類繼承RequestBodyAdviceAdapter,進行入參解密

@ControllerAdvice
@Slf4j
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {

    @Autowired
    private AesUtil aesUtil;

    @Override
    public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 當某個接口方法或參數上加了@Decrypt注解,才進行解密操作
     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 {

        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 , "www.cnblogs.com/shamo89");
       // 把入參修改為解密後的内容      
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptData.getBytes());
            return new HttpInputMessage() {
                @Override
                public InputStream getBody() throws IOException {
                    return byteArrayInputStream;
                }

                @Override
                public HttpHeaders getHeaders() {
                    return inputMessage.getHeaders();
                }
            };
        } catch (Exception e) {
            log.error("接口入參解密出錯:{}", Throwables.getStackTraceAsString(e));
        }

        return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
    }
}      

3、建立自定義EncryptResponseAdvice類繼承ResponseBodyAdvice,進行出參加密

這裡的R是自定義的接口傳回封裝類

@ControllerAdvice
@Slf4j
public class EncryptResponseAdvice implements ResponseBodyAdvice<R> {

    @Autowired
    private AesUtil aesUtil;

    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
     // 隻有接口上加了@Encrypt注解才進行出參加密操作
return returnType.hasMethodAnnotation(Encrypt.class);
    }

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

        try {
            if (body.getData() != null) {
                log.info("接口加密前傳回的資料:{}", JSONUtil.toJsonStr(body.getData()));
                String encStr = aesUtil.encrypt(JSONUtil.toJsonStr(body.getData()), "www.cnblogs.com/shamo89");      
log.info("接口加密後傳回的資料:{}", encStr);
                body.setData(encStr);
            }
        } catch (Exception e) {
            log.error("接口傳回資料加密出錯:{}", Throwables.getStackTraceAsString(e));
        }
        return body;
    }
}      

4、controller接口

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

三、原理說明

上面那兩個advice類型,都要使用@ControllerAdvice注解進行修飾,它其實是一個實作特殊功能的@Component,隻是針對controller進行攔截,本質還是aop,我們平常使用的全局異常處理類@ExceptionHandler,GlobalExceptionHandler,也是配合該注解使用。