JCaptcha做驗證碼遇到的問題引出的思考


JCaptcha用來做用戶登錄時期的驗證碼的,但是今天將開放的應用系統部署到生產環境的時候,遇到了問題,總是提示驗證碼不對。后台報出來下面的錯誤:

 1  com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting or already validated captcha  2         at com.octo.captcha.service.AbstractCaptchaService.validateResponseForID(AbstractCaptchaService.java:146)
 3         at com.octo.captcha.service.AbstractManageableCaptchaService.validateResponseForID(AbstractManageableCaptchaService.java:367)
 4         at com.tk.cms.core.shiro.JCaptcha.validateResponse(JCaptcha.java:19)
 5         at com.tk.cms.core.shiro.JCaptchaValidateFilter.isAccessAllowed(JCaptchaValidateFilter.java:44)
 6         at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162)
 7         at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203)
 8         at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178)
 9         at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131)
10         at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
11         at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66)
12         at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449)
13         at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365)
14         at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90)
15         at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83)
16         at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383)
17         at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362)
18         at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125)
19         at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:343)
20         at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:260)
21         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
22         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
23         at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
24         at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:106)
25         at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
26         at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
27         at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
28         at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
29         at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
30         at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
31         at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
32         at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956)
33         at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
34         at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
35         at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
36         at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625)
37         at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
38         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
39         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
40         at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
41         at java.lang.Thread.run(Thread.java:745)

網上也有很多種方案,其中就說到過,如下代碼處有問題(紅色部分代碼執行的不是時候):

 1     /**
 2      * Method to validate a response to the challenge corresponding to the given ticket and remove the coresponding
 3      * captcha from the store.
 4      *
 5      * @param ID the ticket provided by the buildCaptchaAndGetID method
 6      * @return true if the response is correct, false otherwise.
 7      * @throws CaptchaServiceException if the ticket is invalid
 8      */
 9     public Boolean validateResponseForID(String ID, Object response)
10             throws CaptchaServiceException {
11         if (!store.hasCaptcha(ID)) {
12             throw new CaptchaServiceException("Invalid ID, could not validate unexisting or already validated captcha");
13         } else {
14             Boolean valid = store.getCaptcha(ID).validateResponse(response);
15  store.removeCaptcha(ID); 16             return valid;
17         }
18     }

其實,我也仔細debug過,這個驗證碼錯誤的問題,其實是在store這個FastHashMap中通過sessionId為key去找是否存在這么一個jcaptcha實例,若沒有就報exception了,最后就是驗證碼錯誤。 其實,這不是我應用中的問題。 

 

針對這個問題,我一開始,懷疑是自己的代碼寫的出了問題,總在分析代碼的流程,但是疑點是,我直接訪問tomcat所在的機器,沒有出現驗證碼的錯誤。但是一旦上到nginx的負載均衡環境,就遇到這個問題。想想,為何session不對???看看應用中的日志,sessionID和瀏覽器中cookie中的sessionId,總是不一樣。。。這個就是問題的表象,根源在什么地方呢???

 

后來仔細看了看我們服務器的nginx的反向代理配置,發現upstream部分,沒有配置負載均衡的策略,什么都沒有配置呀。。。。我倒,什么都沒有指定,那就是default的roundrobin啊,輪詢啊。。。。我的神,這一個應用服務會有多少次http請求到達后端啊,每次輪詢,那session對於后端的服務來說,豈不是沒有地方hold了。。。不行。這個就是問題的根源。。。

 

我們的session沒有專門的共享方案,所以,為了改動最小化,最好不改代碼的情況下,我選擇將upstream部分添加ip_hash策略,這樣子能保證同一個IP請求的所有http都鎖定在后端的一個服務上,這樣子就不存在session丟失的問題了。

1  upstream cms {
2       server 10.130.14.51:8080;
3       server 10.130.14.53:8080;
4  ip_hash; 5     }

加上了上面的紅色部分,問題解決!

 

 

思考:

1. 負載均衡環境下,session的管理是個問題,忽視這個問題,會造成登錄都不可能完成。 遇到登錄或者類似我這里驗證碼總是不對的情況,可以想想,是不是session的管理不到位!

2. 常見的session管理,比較靠譜的可控的方式有類似我這里的方案,基於ip_hash的策略,還有,就是專門的開發接口來管理session,不用tomcat容器的那一套。比如將session放在mysql里面,或者redis等中間件里面。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM