【解决问题思路分析】记录hutool默认使用服务端上次返回cookie的问题解决思路

背景:

本服务需要调用第三方接口获取数据,首先调用public-key接口获取公钥,然后用公钥加密密码,将用户名和密码传入/ticket接口,获取Cookie和response body中的token。
请添加图片描述

排查思路

由于是调用第三方接口出现问题,第一步先拉通第三方对接人查看后台日志,对方使用apisix作为api网关,初步判断是对方有不同节点,负载均衡到不同节点而导致的偶发性问题。因此本地使用postman连续发起多次请求,经过50次请求后,每一次都能正确响应结果,此时可推测应该不是由于负载均衡不同节点导致的。

思路二:既然postman每次都能成功,业务系统却会偶发性,说明问题应该是出在本地代码。Debug本地调用流程以期望能够复现302问题,经过多次运行发现,第三方接口返回的cookie和token(下面将cookie和token统称为凭证)会存储在redis中。

代码中判断如果redis中没有凭证,则会重新获取并保存在redis中,如果redis中有凭证则从redis中获取。而redis中没有凭证时,调用第三方接口就获取凭证,再调用第三方业务接口时就一定会报错302,若直接从redis中获取凭证则不会有该问题。此时,可以复现302问题。

既然从redis中获取是正确的,而第一次请求过来就是302错误,我们对比两者的凭证,发现凭证时一模一样的。此时和对接人讨论,怀疑可能第是三方bug:在第一次请求时会查找本地缓存凭证,如果没有的话,则报错302,第二次就能在缓存中找到。为了验证该假设,我们用postman去做第一次调用,发现postman在第一次调用也是能够正常返回的,此时陷入僵局。

思路三:此时,想到通过wireShark抓包查看本地发出的请求有什么区别,但由于是https请求,却看不到header信息。

思路四:由于我们能准确复现302问题,我们再次拉通第三方,期望从对方日志中发现异常,从日志中发现,我们传过去的header中有两个cookie,但是我们代码中只传了一个cookie,此时推测可能是hutool在第一次获取服务端请求时把Cookie缓存起来,下次调用就默认带上Cookie。

HttpRequest req = HttpUtil.createRequest(Method.GET, url) 
							.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)  
							  .header("Cookie", "Cookie")     
							  .header("CSRFPreventionToken", "CSRFPreventionToken") 
							  .header("client_token", "client_token");
HttpResponse response =req.execute();

因此我们使用Apache来调用验证,发现Apache调用并不会出现302问题,此时可确认该问题是由于hutool工具会缓存Cookie,经过debug,发现确实会缓存到threadLocal中。最终,通过在调用/ticket接口并获取完数据后,加上下面一行代码解决。

HttpRequest.getCookieManager().getCookieStore().removeAll();

问题原因

hutool 自动保存了我们 /ticket 返回的 cookie,在这我们重新设置了一遍 token、cookie,导致重复了;然后为什么第二次访问没问题,第一次的request已经结束了,Cookie也就被清掉了,所以只有我们设置的token、Cookie。

总结

本次问题的解决最终找到hutool缓存上一次服务端返回的Cookie是不容易的,中间甚至还用抓包解决,这些解决思路值得记录。通过复盘分析, wireShark是个很好的定位问题工具,需要花点时间学习;开源包的issue是个解决问题的好思路,可以在上面搜索一下;当然还有复现问题,第三方协助,这些都很重要。

最后感谢自己,絮絮叨叨完成了博客,期待写出更多博客哦

参考博客

https://github.com/dromara/hutool/issues/583
https://blog.youkuaiyun.com/weixin_30315905/article/details/97599019

### 若依前后端分离验证码不显示解决方案 对于若依前后端分离项目中的验证码不显示问题,可以采取以下措施来确保验证码能够正常加载并展示。 #### 1. 后端配置Easy-Captcha依赖 为了实现图形验证码功能,在Spring Boot项目的`pom.xml`文件中添加Easy-Captcha库的相关Maven依赖[^3]: ```xml <dependency> <groupId>com.github.whvcse</groupId> <artifactId>easy-captcha</artifactId> <version>1.6.2</version> </dependency> ``` 这一步骤是为了引入必要的工具类用于生成图像形式的验证码。 #### 2. 修改后端接口返回格式 当客户端请求获取验证码时,服务器应将验证码图片转换成Base64编码字符串的形式发送给前端。这样做的好处是可以直接嵌入到HTML页面内而不必单独处理静态资源路径问题[^2]。 例如可以在控制器方法里加入如下逻辑: ```java import cn.hutool.extra.spring.SpringUtil; // ...其他导入语句... @GetMapping("/captchaImage") @ResponseBody public Map<String, Object> getCaptcha() { EasyCaptcha captcha = new DefaultGifCaptcha(130, 48); String code = captcha.text(); // 将验证码存入Redis缓存以便后续验证使用 RedisTemplate redisTemplate = SpringUtil.getBean(RedisTemplate.class); redisTemplate.opsForValue().set("loginCode", code.toLowerCase(), 5L, TimeUnit.MINUTES); try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { captcha.out(outputStream); byte[] bytes = outputStream.toByteArray(); Base64.Encoder encoder = Base64.getEncoder(); String base64Img = "data:image/png;base64," + encoder.encodeToString(bytes); HashMap<String, Object> resultMap = Maps.newHashMapWithExpectedSize(2); resultMap.put("img", base64Img); // 返回base64编码后的图片 resultMap.put("uuid", UUID.randomUUID().toString()); // 可选:为每次请求分配唯一标识符 return resultMap; } catch (IOException e) { throw new RuntimeException(e.getMessage()); } } ``` 这段代码实现了通过HTTP GET方式提供一张新的随机验证码图片,并将其以Base64编码的方式封装进JSON对象一同响应给调用者。 #### 3. 处理跨域资源共享(CORS) 由于采用了前后端分离架构设计模式,因此可能会遇到浏览器同源策略限制的问题。此时可以通过设置允许携带凭证属性(`withCredentials`)以及适当调整服务端的安全策略来解决问题[^4]: 在Vue/React等前端框架下修改axios默认配置: ```javascript import axios from 'axios'; axios.defaults.baseURL = process.env.VUE_APP_BASE_API; axios.defaults.headers.post['Content-Type'] = 'application/json'; axios.defaults.withCredentials = true; // 允许跨域携带Cookie ``` 同时还需要确认Nginx或其他Web服务器已经正确设置了Access-Control-Allow-Origin等相关头部信息,使得不同域名下的网页也能合法访问API接口。 #### 4. Nginx反向代理配置优化 考虑到实际生产环境中往往还会借助于Nginx来做负载均衡和静态资源分发等工作,则有必要仔细检查其配置项是否合理有效。特别是针对静态文件(如上传的照片、图标等)可能出现的404错误情况作出针对性改进[^1]。 比如下面是一个简单的location匹配规则片段供参考: ```nginx server { listen 80; server_name localhost; location /api/ { proxy_pass http://backend_server_address/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; add_header Access-Control-Allow-Origin * always; add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS' always; add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization' always; if ($request_method = 'OPTIONS') { return 204; } # 对于POST PUT DELETE等操作需要开启此选项才能让浏览器带上认证信息(cookie/sessionid) proxy_cookie_path / "/; HttpOnly"; } location ~* \.(jpg|jpeg|png|gif)$ { root html/static/images/; expires max; access_log off; } } ``` 上述配置不仅解决了可能存在的跨域难题,还特别指定了如何高效地服务于各类多媒体素材,从而间接促进了验证码图片的成功渲染。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值