由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来实现的。看下面的图吧(不专业,见笑了):

《由Nginx反向代理引出的JCaptcha验证码验证失败的问题》

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 参考资料

回到目录

四 一路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,也不要浮躁,花点时间去研究与实践,总会有收获的😄。

六 参考资料

回到目录

转载请说明出处!have a good time 😄 ~

点赞