今天在调试某个接口的时候 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就解决了!
另外还有个疑问就是还是之前那个问题 如果说响应体太大超出了限制的话 那我那个查询全部的接口数据不比现在大的多嘛 为啥可以正常!
有知道的大佬给解个答!非常感谢!
另外感谢我们的安卓小姐姐!帮我找到了问题所在!不然估计还能找一天。。。。哈哈
有时候人就容易走进自己设计的逻辑误区!当局者迷嘛!涨个教训!
?
|