@ResponseBodyAdvice & @RequestBodyAdivce失效

背景

最近项目要有向外部提供服务的能力,但是考虑到数据安全问题,要对接口进行加解密;实现加解密的方案有很多,比如过滤器、拦截器、继承RequestResponseBodyMethodProcessor什么的,不过我最近正在了解@ResponseBodyAdvice @RequestBodyAdvice这俩注解,本着在实践中应用的目的,就准备使用这两个注解来实现加解密功能。

然而,配置好后,请求怎么都进不到这两个注解的类里。摸索了一天的时间,@RestController 和@ResponseBody 都加了,也确认已经扫描进容器中管理了,可就是无法生效。

原因

后来发现项目中之前有对所有的controller进行返回结果的统一包装,使用的是继承RequestResponseBodyMethodProcessor类来实现;

刚刚@ResponseBodyAdvice和@RequestBodyAdvice一直无法生效,就在RequestResponseBodyMethodProcessor这里面做了加密的动作,后来不经意间,把这个类在WebMvcConfigurer中导入的代码注掉了,惊奇的发现@ResponseBodyAdvice @RequestBodyAdvice这俩注解生效 了。

所以初步定位 @ResponseBodyAdvice @RequestBodyAdvice 和RequestResponseBodyMethodProcessor 会冲突导致不生效。

解决

RequestResponseBodyMethodProcessor 里的逻辑抽取到@ResponseBodyAdvice里,本来这个也是对返回结果进行增强的,所以放到这里也非常合理。

同时扩展了加密的逻辑。

核心代码

@ControllerAdvice
public class ResponseProcessor implements ResponseBodyAdvice<Object> {
    private ObjectMapper om = new ObjectMapper();
    @Autowired
    EncryptProperties encryptProperties;

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        return methodParameter.hasMethodAnnotation(Encrypt.class);
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        byte[] keyBytes = encryptProperties.getKey().getBytes();
        try {
            if(!methodParameter.hasMethodAnnotation(NoResponseWrapperAnnotation.class)){
                body = new ResponseWrapper<>(body);
            }
            body = AESUtils.encrypt(JSONObject.toJSONString(body),encryptProperties.getKey());

        } catch (Exception e) {
            e.printStackTrace();
        }
        return body;
    }
}
```


```java
@ControllerAdvice
public class RequestProcessor extends RequestBodyAdviceAdapter {
    @Autowired
    private 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 {
            String decrypt = AESUtils.decrypt(new String(body), encryptProperties.getKey());
            final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt.getBytes());
            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);
    }
}
```

```java
public class AESUtils {
    private static final String KEY_ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法

    public static String getKey(int len){
        if(len % 16 != 0){
            System.out.println("长度要为16的整数倍");
            return null;
        }

        char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray();
        char[] uuid = new char[len];

        if (len > 0) {
            for (int i = 0; i < len; i++) {
                int x = (int) (Math.random() * (len - 0 + 1) + 0);
                uuid[i] = chars[x % chars.length];
            }
        }

        return new String(uuid);
    }


    public static String byteToHexString(byte[] bytes){
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < bytes.length; i++) {
            String strHex=Integer.toHexString(bytes[i]);
            if(strHex.length() > 3){
                sb.append(strHex.substring(6));
            } else {
                if(strHex.length() < 2){
                    sb.append("0" + strHex);
                } else {
                    sb.append(strHex);
                }
            }
        }
        return  sb.toString();
    }

    /**
     * AES 加密操作
     *
     * @param content 待加密内容
     * @param key 加密密码
     * @return 返回Base64转码后的加密数据
     */
    public static String encrypt(String content, String key) {
        try {
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器

            byte[] byteContent = content.getBytes("utf-8");

            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器

            byte[] result = cipher.doFinal(byteContent);// 加密

            return org.apache.commons.codec.binary.Base64.encodeBase64String(result);//通过Base64转码返回
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    /**
     * AES 解密操作
     *
     * @param content
     * @param key
     * @return
     */
    public static String decrypt(String content, String key) {

        try {
            //实例化
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);

            //使用密钥初始化,设置为解密模式
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));

            //执行操作
            byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(content));

            return new String(result, "utf-8");
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    private static SecretKeySpec getSecretKey(final String key) throws UnsupportedEncodingException {
        //返回生成指定算法密钥生成器的 KeyGenerator 对象
//        KeyGenerator kg = null;

        //            kg = KeyGenerator.getInstance(KEY_ALGORITHM);
//
//            //AES 要求密钥长度为 128
//            kg.init(128, new SecureRandom(key.getBytes()));
//
//            //生成一个密钥
//            SecretKey secretKey = kg.generateKey();

        return new SecretKeySpec(Arrays.copyOf(key.getBytes("utf-8"), 16), KEY_ALGORITHM);// 转换为AES专用密钥

    }
}
```
相关推荐
捕鲸叉14 分钟前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer18 分钟前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq20 分钟前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml444 分钟前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~1 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端
hong1616881 小时前
Spring Boot中实现多数据源连接和切换的方案
java·spring boot·后端
aloha_7891 小时前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
记录成长java2 小时前
ServletContext,Cookie,HttpSession的使用
java·开发语言·servlet
前端青山2 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
睡觉谁叫~~~2 小时前
一文解秘Rust如何与Java互操作
java·开发语言·后端·rust