由Nginx反向代理引出的JCaptcha验证码验证失败的问题
搜索关键字:
1)Windows本地开发正常,部署到Linux远程服务器上JCaptcha验证失败
2)Linux远程服务器上JCpatcha验证失败
3)Nginx反向代理后JCaptcha验证失败
目录
一 前言
我为什么要写这篇文章?
很简单,因为从我遇到这个问题到解决这个问题,途中花了不少时间,查了不少资料,改了不少代码,验证了不少猜想。然而,最后解决问题,只需要在 nginx.conf 中加一行配置即可。
为什么这么一个 “小问题” 要花我这么多时间呢?
因为 “JCpatcha验证码验证失败” 只是表象,问题的本质原因是 “Nginx反向代理导致Session丢失”。而大多数对知识点没有深入理解的、缺乏经验的同学(比如我),一开始都只会根据表象去查询解决方案,收效甚微。使用 “Nginx反向代理导致Session失效” 等关键字去查,解决方案一查一大堆,而使用 “Linux服务器下JCaptcha验证码失败” 类似的关键字去搜索,往往很难找到解决该问题的方法,因为该问法的范围较广,没有针对性(抓住关键点)。
所以,我写了这篇文章,并且特意在文章顶部写了搜索关键字,希望可以帮助遇到同样问题的同学提高搜索效率。除了写解决问题的方法外,我还贴出了从遇到这个问题到解决问题这一路的Debug过程,或许我思考问题的方式、验证猜想的方法等可以给大家一些帮助😁。
二 背景描述
最近写工程实践项目,使用了JCaptcha来做验证码,在Windows下本地测试是正常的,但部署到远程Linux服务器后,发现用户登录时(不单是用户登录,其他用到验证码的功能都一样),始终返回“验证码错误”。
之前在Windows下开发测试时,验证码部分遇到过的报错是:“com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting or already validated captcha”。我以为也是这个问题呢,结果一看日志,啥报错都没有。
三 问题&解决
1 问题
问题的根本原因,其实从文章标题就可以看出,就是 使用Nginx反向代理后Session丢失了,而JCpatcha是基于Session来实现的。看下面的图吧(不专业,见笑了):
2 解决
既然是 Nginx反向代理session丢失引起的问题,那我们让 Nginx反向代理时保持session不就好了。关于 解决Nginx反向代理session丢失问题 的方法网上一查一大堆,而我是用下面的方法(简单加一行配置)解决的。
> sudo vim /etc/nginx/nginx.conf,添加:proxy_cookie_path /idevtools /; # 保持session,根据你的项目更改path,proxy_cookie_path [path1] [path2]; 后面接的是2个path,注意它们的顺序;改完保存、退出,> sudo service nginx restart,OK~打完收工。
user www-data; worker_processes auto; pid /run/nginx.pid; events { worker_connections 768; # multi_accept on; } http { ## 省略其他配置## # SSL Settings # add https ssl [southday 2018.11.1 v1] server { listen 443; server_name www.idevtools.cn; ssl on; ssl_certificate /etc/nginx/cert/cert-1540964531179_www.idevtools.cn.crt; ssl_certificate_key /etc/nginx/cert/cert-1540964531179_www.idevtools.cn.key; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; # 关键步骤:将https://idevtools.cn/ 映射到 http://localhost:8090/idevtools/ location / { proxy_pass http://localhost:8090/idevtools/;
proxy_cookie_path /idevtools /; # 保持session } } ## 省略其他配置 }
3 参考资料
- 解决nginx使用proxy_pass反向代理时,session丢失的问题:https://user.qzone.qq.com/737816745/blog/1422497717?_t_=0.6621641832613769
- jsessionid:https://blog.csdn.net/yingevil/article/details/6916550
- 对JESSIONID理解:https://blog.csdn.net/yinbucheng/article/details/69802911
- Nginx-proxy_cookie_path:http://nginx.org/en/docs/http/ngx_http_proxy_module.html?&_ga=1.161910972.1696054694.1422417685#proxy_cookie_path
四 一路Debug
一次好的debug就像是一场探险寻宝之旅,翻山越岭,披荆斩刺,最终找到宝藏。在这个过程中,不仅可以增长你的经验,还可以加深你对事物的认知,最后还解决了问题,拿到了专属于你的宝藏。
所以,不要轻易放过一个bug,也不要浮躁,花点时间去研究与实践,总会有收获的😄。下面的内容就是我在遇到这个问题(Windows本地开发测试正常,部署到Linux服务器上JCaptcha验证失败)时的探险寻宝之旅~
1 为什么Windows本地开发测试正常,部署到Linux服务器上JCaptcha验证失败?
第一反映,查日志,看看是不是报了什么异常,发现日志中没有关于JCaptcha验证的异常信息。
2 为什么没有异常信息,却返回验证失败呢?
看代码,包括JCaptcha验证的部分源码。JCaptcha中验证方法的入口是:com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet 类的 validateResponse() 方法,源码如下:
public static boolean validateResponse(HttpServletRequest request, String userCaptchaResponse) { if (request.getSession(false) == null) { return false; } else { boolean validated = false; try { validated = service.validateResponseForID(request.getSession().getId(), userCaptchaResponse); } catch (CaptchaServiceException var4) { var4.printStackTrace(); } return validated; } }
注意,我直接找该方法来排查问题,是因为我已经确保了我的调用流程中,其他步骤都能正确获取到值、正确返回结果。如果你还不能确保,那还是老老实实run代码,测试一遍。好多时候找不到bug是因为细节问题没处理好,还有自以为是的 “啊,这个地方没有问题的,我保证!”😂
其实到这里,问题也就很明显了,没有报异常,那很有可能就是代码走了这条路径:
if (request.getSession(false) == null) { return false; }
我为什么这么敢肯定呢?因为我是有办法让 “com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting or already validated captcha” 这个异常重现的,而我部署到服务器上时,实施了重现这个异常的操作,发现却没有任何报错信息。
(上述的都是后话,因为我从没想过会有session丢失这种问题,思维被定式到了“异常信息没有展示”上,所以走了一些弯路,但也学了一些东西)
3 弯路
我的思维被定式到了“异常信息”上,所以在想,是不是代码内部其实抛了异常,只是部署到服务器上,var4.printStackTrace() 的内容不会打印到日志中?
顺着这个思路,我找了:如何将 printStackTrace() 中的内容打印到日志中。查到的好多结果都是:用Logger记录就OK,差不多就是下面的语句:
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; static Logger logger = LogManager.getLog(SimpleImageCaptchaServlet.class); ... try { validated = service.validateResponseForID(request.getSession().getId(), userCaptchaResponse); } catch (CaptchaServiceException var4) { logger.warn(var4); // 或者 logger.warn(ExceptionUtils.getStackTrace(var4))); } ...
但是,SimpleImageCaptchaServlet 是JCaptcha的源码啊,难道我要下载一份源码,自己DIY,重新编译打包,然后在项目中添加DIY后的jar依赖吗?其实事情没那么复杂,我只需要重新写一个类:MySimpleImageCaptchaServlet.java,其内部使用和SimpleImageCaptchaServlet差不多的实现,只是自己加了一些打印语句(方便调试),然后 web.xml 中配置 /jcaptcha 的Servlet为MySimpleImageCaptchaServlet即可。
通过上述的方法,构造了MySimpleImageCaptchaServlet.java,如下(只贴validateResponse()部分):
public static boolean validateResponse(HttpServletRequest request, String userCaptchaResponse) {if (request.getSession(false) == null) { return false; } else { boolean validated = false; try { logger.info("[before] validateResponse(), validated = " + validated); validated = service.validateResponseForID(request.getSession().getId(), userCaptchaResponse); logger.info("[after] validateResponse(), validated = " + validated); } catch (CaptchaServiceException var4) { logger.warn("验证码验证异常:" + ExceptionUtils.getStackTrace(var4)); } return validated; } }
重新部署到服务器上,再次测试,发现日志中依旧没有异常信息,此外,连 “[before] validateResponse(), validated = ”这些信息都没打印,这不就是没执行 try{}catch{}中的语句嘛!!!
弯路到此结束,我已经知道是走上面的 if(request.getSession(false) == null) {} 路径了。
4 那么 request.getSession(false) 为什么为空呢?
想起来了,在Windows上开发测试,与部署到Linux服务器上的区别,除了操作系统外,还有一个比较重要的地方被我遗漏了,Nginx反向代理!
(事后,我把Nginx关了,直接用 http://idevtools.cn:8090/idevtools/ 去测试,是可以成功登录的)
于是在网上查:Nginx反向代理、request.getSession(),一大堆资料。(这里吐槽一下CSDN和那些Copyer,我查个资料,前7条都是一样的标题名称???点进去一看,内容几乎差不多,能来个原创吗?CSDN不能用印象笔记浏览器插件剪贴文章是什么鬼?f++u)
剩下的内容就可以参考第三部分了,👉 转送门
五 总结
虽然我这里的debug过程只列出了4步,但真实情况要比这还麻烦一些,只是出于时间关系,以及问题描述等原因,我做了一些简化。总得来说,这次debug之旅还是有些收获的,也不枉费我花了这么多时间。最后,one more time,一次好的debug就像是一场探险寻宝之旅,翻山越岭,披荆斩刺,最终找到宝藏。在这个过程中,不仅可以增长你的经验,还可以加深你对事物的认知,最后还解决了问题,拿到了专属于你的宝藏。所以,不要轻易放过一个bug,也不要浮躁,花点时间去研究与实践,总会有收获的😄。
六 参考资料
- 解决nginx使用proxy_pass反向代理时,session丢失的问题:https://user.qzone.qq.com/737816745/blog/1422497717?_t_=0.6621641832613769
- nignx反向代理后获取不到正确的ip以及请求头:https://blog.csdn.net/qq_38243970/article/details/7898392
- jsessionid:https://blog.csdn.net/yingevil/article/details/6916550
- 对JESSIONID理解:https://blog.csdn.net/yinbucheng/article/details/69802911
- Nginx-proxy_cookie_path:http://nginx.org/en/docs/http/ngx_http_proxy_module.html?&_ga=1.161910972.1696054694.1422417685#proxy_cookie_path
- 如何获取e.printStackTrace()的内容:https://blog.csdn.net/z69183787/article/details/46645399
- Tomcat日志、项目中的log4j日志、e.printStackTrace()——我的日志最后到底跑哪去了?:https://www.cnblogs.com/flying607/articles/6293995.html
转载请说明出处!have a good time 😄 ~