IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringCloud 请求报错 Input length must be multiple of 16 when decrypting with padded cipher -> 正文阅读

[Java知识库]SpringCloud 请求报错 Input length must be multiple of 16 when decrypting with padded cipher

今天在调试某个接口的时候 app那边反馈说其中一个接口加密的数据解密不出来。

一开始我没当回事 觉得是他们的解密方法有问题,直到他们百般尝试以后还是不行才引起了我的注意!

于是就来调试一下吧。反正闲着也是闲着!

分析:一开始可能觉得是api服务端返回的数据有问题导致AES加密的串出了问题 所以导致没发解密!

后来一顿分析 过程如下!

报错信息

报错的接口:

当时在这个接口服务分析错误 花费了非常多时间 大概是觉得原因有两点

1:这个接口返回的数据有问题 可能会有特殊符号导致无法被返回最终加密失败

2:改接口返回的封装对象序列化有问题 导致请求数据在过网关时没有被完整的接收!

? ? 后经过验证出现了一个离奇的问题就是当该接口我传递参数 按某个条件查找时候接口会报错

可以看上图接口传了个code;

接着看

List<Area> areas=areaService.list(new LambdaQueryWrapper<Area>().eq(Area::getCityCode,code));

修改后

 List<Area> areas=areaService.list();

?区别是拿掉了条件查找 返回结果如下:

发现可以正常返回

来对比下返回数据的结果吧?

按条件查找:也就是会出现AES解密报错的接口返回数据

{
    "error":false,
    "code":200,
    "msg":"操作成功",
    "debug":null,
    "body":[
        {
            "code":"330102",
            "name":"上城区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330105",
            "name":"拱墅区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330106",
            "name":"西湖区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330108",
            "name":"滨江区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330109",
            "name":"萧山区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330110",
            "name":"余杭区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330111",
            "name":"富阳区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330112",
            "name":"临安区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330113",
            "name":"临平区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330114",
            "name":"钱塘区",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330122",
            "name":"桐庐县",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330127",
            "name":"淳安县",
            "cityCode":"3301",
            "provinceCode":"33"
        },
        {
            "code":"330182",
            "name":"建德市",
            "cityCode":"3301",
            "provinceCode":"33"
        }
    ]
}

全部数据:正常数据 数据太多就省略了

{
    "error":false,
    "code":200,
    "msg":"操作成功",
    "debug":null,
    "body":[
        {
            "code":"110101",
            "name":"东城区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110102",
            "name":"西城区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110105",
            "name":"朝阳区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110106",
            "name":"丰台区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110107",
            "name":"石景山区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110108",
            "name":"海淀区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110109",
            "name":"门头沟区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110111",
            "name":"房山区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110112",
            "name":"通州区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110113",
            "name":"顺义区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110114",
            "name":"昌平区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110115",
            "name":"大兴区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110116",
            "name":"怀柔区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110117",
            "name":"平谷区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110118",
            "name":"密云区",
            "cityCode":"1101",
            "provinceCode":"11"
        },
        {
            "code":"110119",
            "name":"延庆区",
            "cityCode":"1101",
            "provinceCode":"11"
        }

可以看数据没有任何区别

网关代码:

package com.fuhang.mall.filter;

import cn.hutool.core.codec.Base64Encoder;
import com.fuhang.mall.constant.ConstantFilter;
import com.fuhang.mall.constant.OrderedConstant;
import com.fuhang.mall.utils.AES;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

import static com.fuhang.mall.response.ResponseModel.DATA_PASSWORD;

/**
 * @ClassName ${NAME}.java
 * @author 神秘的凯
 * @version 1.0.0
 * @Description 接口返回的数据进行加密处理
 * @createTime 2022/3/17 10:52 上午
 */
@Component
public class ApiResponseEncryptFilter implements GlobalFilter, Ordered {
    @Override
    public int getOrder() {
        return OrderedConstant.RESP_ENCRYPT;
    }
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String bgDebug = exchange.getRequest().getHeaders().getFirst(ConstantFilter.BG_DEBUG_KEY);
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux) {
                    Flux<? extends DataBuffer> fluxBody = (Flux<? extends DataBuffer>) body;
                    return super.writeWith(fluxBody.buffer().map(dataBuffer -> {
                        DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                        DataBuffer join = dataBufferFactory.join(dataBuffer);
                        byte[] content = new byte[join.readableByteCount()];
                        join.read(content);
                        //释放掉内存
                        DataBufferUtils.release(join);
                        // 正常返回的数据
                        String rootData = new String(content, StandardCharsets.UTF_8);
                        byte[] respData = rootData.getBytes();
                        String responseStr = new String(respData, StandardCharsets.UTF_8);
                        if(!ConstantFilter.REQ_RES_ENCRYPT.equals(bgDebug) || !responseStr.toLowerCase(Locale.ROOT).contains("error")){
                                byte[] encode = AES.encrypt(responseStr, DATA_PASSWORD);
                                //传输过程,不转成16进制的字符串,就等着程序崩溃掉吧
                                respData = Base64Encoder.encode(encode).getBytes(StandardCharsets.UTF_8);
                        }
                        // 加密后的数据返回给客户端
                        byte[] uppedContent = new String(respData, StandardCharsets.UTF_8).getBytes();
                        //originalResponse.getHeaders().setContentLength(uppedContent.length);
                        return bufferFactory.wrap(uppedContent);
                    }));
                }
                return super.writeWith(body);
            }
        };
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }
}

在百事不得其解下 我们的安卓大佬登场了 说会不会是网关过来的加密串有问题!

于是就对对比了网关加密后的串和 客户端收到的串。

网关加密后的串如下:

?

Jl5b9G0xhDuCfHfUbxhpy9f1CSfmPWw9105QY9yc/NEZ3QG8RvkGqcourOdG8eT9HfG9U9AkifG2G4MutChCMHIFuzvS4HxHz6mh48eh/XkNwi+CPT/jePdszQQRrIXB0WUH/eI16WTdmJIUFrVzOMB77wJAxhcTKIpu6rn28RzadzaGqPsD4FFFR8g7nWjROQ5EGTNfTXy2ggcgaxss5lGJVDX9CDHWBn2XJBKfHz2+lOgmHR85Nyk963KFoxSC3EQpN+i9+6zg1JyjtqKShhCno/URctJHeU5mMZpzjqQAYpKhz2TJq5SdMC23fOYc529fsPfy/m7zFedSvSdlC63m8ALG3LGb5zNnRWqvvViTHQrvN4lF74LT4fbKCq+ScyjPrRBq/ZoHx2yVjOLA5Ma/y1ZVKWjFuwH5U90qW1TPN/SafkflWQBo8NvcR/oEhlvHiyDRsjrNLa

客户端拿到的串:

Jl5b9G0xhDuCfHfUbxhpy9f1CSfmPWw9105QY9yc/NEZ3QG8RvkGqcourOdG8eT9HfG9U9AkifG2G4MutChCMHIFuzvS4HxHz6mh48eh/XkNwi+CPT/jePdszQQRrIXB0WUH/eI16WTdmJIUFrVzOMB77wJAxhcTKIpu6rn28RzadzaGqPsD4FFFR8g7nWjROQ5EGTNfTXy2ggcgaxss5lGJVDX9CDHWBn2XJBKfHz2+lOgmHR85Nyk963KFoxSC3EQpN+i9+6zg1JyjtqKShhCno/URctJHeU5mMZpzjqQAYpKhz2TJq5SdMC23fOYc529fsPfy/m7zFedSvSdlC63m8ALG3LG

?一对比发现客户端接收的串少了一小段:我特么的。。。。。。。

卧槽了?

现在好了知道问题出在哪了

忘了告诉你们了 这期间我还找了两个阿里的大佬帮忙远程了一个下午找bug? 没找到原因 大家都迷糊着说为啥按条件查找的数据无法解密 而查全部的就可以 所以直接排除了AES解密加密问题和响应体大小限制问题。因为你全部查询的数据肯定比按条件查询的数据多的多,那么多的数据没问题 那少的肯定也没问题了。

? 好了 不叭叭了 说问题吧

根据上面的分析原因 是因为客户端接收的串丢失了 所以想到了是不是Spring Gateway网关?的响应体要设置个什么大小之类的 因为我以前淌过这个坑 所以立马想到了问题所在。

springGatew存在一个分段传输问题

于是我:

?最终得到了答案 在网关的请求响应过滤器内加上响应数据的大小即可 代码如下

 originalResponse.getHeaders().setContentLength(uppedContent.length);

设置完毕后看再来测试下

问题解决!!!

分享下测试类

package com.fuhang.mall;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.http.HttpUtil;
import com.fuhang.mall.utils.AES;
import com.fuhang.mall.utils.AesUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.*;

import static com.fuhang.mall.response.ResponseModel.DATA_PASSWORD;

@SpringBootTest(classes = MallGatewayApplicationTests.class)
class MallGatewayApplicationTests {

    @Test
    void contextLoads() throws Exception {
//        LinkedHashMap<String, HttpStatusEnum> enumMap = EnumUtil.getEnumMap(HttpStatusEnum.class);
//        enumMap.forEach((k,v)->{
//            System.out.println("key:value = " + k + ":" + v.msg());
//        });
//        System.out.println(HttpUtil.createGet("http://api.hxtk.com/books/api/list.action?data=all").timeout(1000000).execute().toString());
//        System.out.println(("requestTime".toLowerCase(Locale.ROOT)));


        //OK 的
//        String rs= HttpUtil.createGet("http://192.168.31.199:7001/api/platform/searchCityList").header("BG_DEBUG","1").header("testToken","2gq72h2qrbhx256y0167uf5wd64ls55u").execute().body();
//        System.out.println(rs);
//        byte[] decode = Base64Decoder.decode(rs);
//        System.out.println(new String(Objects.requireNonNull(AES.decrypt(decode, DATA_PASSWORD))));
//        System.out.println("==============》");

        //有毒的
        String  rs= HttpUtil.createGet("http://192.168.31.199:7001/api/platform/searchAreaList?code=3301").header("BG_DEBUG","1").header("testToken","2gq72h2qrbhx256y0167uf5wd64ls55u").execute().body();
        System.out.println(rs);
        //byte[]   decode = Base64Decoder.decode(rs);
//        String s = new String(decode, StandardCharsets.UTF_8);
//        System.out.println(s);
//        byte[] encrypt = AES.decrypt(s.replace(" ","+").getBytes(StandardCharsets.UTF_8), DATA_PASSWORD);
        System.out.println();
//        System.out.println(new String(encrypt,StandardCharsets.UTF_8));
        //System.out.println(new String(Objects.requireNonNull(AES.decrypt(decode, DATA_PASSWORD))));
//        System.out.println(AesUtils.Decrypt(rs,DATA_PASSWORD));
        byte[] decode = Base64Decoder.decode(rs);
        System.out.println(new String(Objects.requireNonNull(AES.decrypt(decode, DATA_PASSWORD))));
    }

}

?

到此bug就解决了!

另外还有个疑问就是还是之前那个问题 如果说响应体太大超出了限制的话 那我那个查询全部的接口数据不比现在大的多嘛 为啥可以正常!

有知道的大佬给解个答!非常感谢!

另外感谢我们的安卓小姐姐!帮我找到了问题所在!不然估计还能找一天。。。。哈哈

有时候人就容易走进自己设计的逻辑误区!当局者迷嘛!涨个教训!

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-09 18:09:53  更:2022-04-09 18:13:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:26:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码