利用注解RequestBodyAdvice实现http请求内容加解密

注解主要用来指定那些需要加解密的controller方法,实现比较简单

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SecretAnnotation {
    boolean encode() default false;
    boolean decode() default false;
}

使用时添加注解在controller的方法上

@PostMapping("/preview")
@SecretAnnotation(decode = true)
public ResponseVO<ContractSignVO> previewContract(@RequestBody FillContractDTO fillContractDTO)  {
    return contractSignService.previewContract(fillContractDTO);
}

请求数据由二进制流转为类对象数据,对于加密过的数据,需要在二进制流被处理之前进行解密,否则在转为类对象时会因为数据格式不匹配而报错。

因此使用RequestBodyAdvice的beforeBodyRead方法来处理。

@Slf4j
@RestControllerAdvice
public class MyRequestControllerAdvice implements RequestBodyAdvice {
    @Override
    public boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasParameterAnnotation(RequestBody.class);
    }

    @Override
    public Object handleEmptyBody(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }

    @Autowired
    private MySecretUtil mySecretUtil;
    @Override
    public HttpInputMessage beforeBodyRead(HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) throws IOException {
        if (methodParameter.getMethod().isAnnotationPresent(SecretAnnotation.class)) {
            SecretAnnotation secretAnnotation = methodParameter.getMethod().getAnnotation(SecretAnnotation.class);
            if (secretAnnotation.decode()) {
                return new HttpInputMessage() {
                    @Override
                    public InputStream getBody() throws IOException {
                        List<String> appIdList = httpInputMessage.getHeaders().get("appId");
                        if (appIdList.isEmpty()){
                            throw new RuntimeException("请求头缺少appID");
                        }
                        String appId = appIdList.get(0);
                        String bodyStr = IOUtils.toString(httpInputMessage.getBody(),"utf-8");

                        bodyStr = mySecretUtil.decode(bodyStr,appId);
                        return  IOUtils.toInputStream(bodyStr,"utf-8");
                    }

                    @Override
                    public HttpHeaders getHeaders() {
                        return httpInputMessage.getHeaders();
                    }
                };
            }
        }
        return httpInputMessage;
    }

    @Override
    public Object afterBodyRead(Object o, HttpInputMessage httpInputMessage, MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {
        return o;
    }
}

mySecretUtil.decode(bodyStr,appId)的内容是,通过请求头中的AppID去数据库中查找对于的秘钥,之后进行解密,返回解密后的字符串。再通过common.io包中提供的工具类IOUtils将字符串转为inputstream流,替换HttpInputMessage,返回一个body数据为解密后的二进制流的HttpInputMessage。

 <dependency>
     <groupId>commons-io</groupId>
     <artifactId>commons-io</artifactId>
     <version>2.6</version>
</dependency>

发表评论