搜索關鍵字:
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 😄 ~