前些天發現了一個巨牛的人工智能學習網站,通俗易懂,風趣幽默,忍不住分享一下給大家。點擊跳轉到教程。
在做用戶登錄功能時,很多時候都需要驗證碼支持,驗證碼的目的是為了防止機器人模擬真實用戶登錄而惡意訪問,如暴力破解用戶密碼/惡意評論等。目前也有一些驗證碼比較簡單,通過一些OCR工具就可以解析出來;另外還有一些驗證碼比較復雜(一般通過如扭曲、加線條/噪點等干擾)防止OCR工具識別;但是在中國就是人多,機器干不了的可以交給人來完成,所以在中國就有很多打碼平臺,人工識別驗證碼;因此即使比較復雜的如填字、算數等類型的驗證碼還是能識別的。所以驗證碼也不是絕對可靠的,目前比較可靠還是手機驗證碼,但是對于用戶來說相對于驗證碼還是比較麻煩的。
?
對于驗證碼圖片的生成,可以自己通過如Java提供的圖像API自己去生成,也可以借助如JCaptcha這種開源Java類庫生成驗證碼圖片;JCaptcha提供了常見的如扭曲、加噪點等干擾支持。
?
一、添加JCaptcha依賴?

- <dependency>??
- ????<groupId>com.octo.captcha</groupId>??
- ????<artifactId>jcaptcha</artifactId>??
- ????<version>2.0-alpha-1</version>??
- </dependency>??
- <dependency>??
- ????<groupId>com.octo.captcha</groupId>??
- ????<artifactId>jcaptcha-integration-simple-servlet</artifactId>??
- ????<version>2.0-alpha-1</version>??
- ????<exclusions>??
- ????????<exclusion>??
- ????????????<artifactId>servlet-api</artifactId>??
- ????????????<groupId>javax.servlet</groupId>??
- ????????</exclusion>??
- ????</exclusions>??
- </dependency>???
com.octo.captcha . jcaptcha?提供了jcaptcha?核心;而jcaptcha-integration-simple-servlet提供了與Servlet集成。
?
二、GMailEngine
來自https://code.google.com/p/musicvalley/source/browse/trunk/musicvalley/doc/springSecurity/springSecurityIII/src/main/java/com/spring/security/jcaptcha/GMailEngine.java?spec=svn447&r=447(目前無法訪問了),仿照JCaptcha2.0編寫類似GMail驗證碼的樣式;具體請參考com.github.zhangkaitao.shiro.chapter22.jcaptcha.GMailEngine。
?
三、MyManageableImageCaptchaService
提供了判斷倉庫中是否有相應的驗證碼存在。?

- public?class?MyManageableImageCaptchaService?extends???
- ??DefaultManageableImageCaptchaService?{???
- ????public?MyManageableImageCaptchaService(??
- ??????com.octo.captcha.service.captchastore.CaptchaStore?captchaStore,????????
- ??????com.octo.captcha.engine.CaptchaEngine?captchaEngine,??
- ??????int?minGuarantedStorageDelayInSeconds,???
- ??????int?maxCaptchaStoreSize,???
- ??????int?captchaStoreLoadBeforeGarbageCollection)?{??
- ????????super(captchaStore,?captchaEngine,?minGuarantedStorageDelayInSeconds,???
- ????????????maxCaptchaStoreSize,?captchaStoreLoadBeforeGarbageCollection);??
- ????}??
- ????public?boolean?hasCapcha(String?id,?String?userCaptchaResponse)?{??
- ????????return?store.getCaptcha(id).validateResponse(userCaptchaResponse);??
- ????}??
- }??
??
?
四、JCaptcha工具類
提供相應的API來驗證當前請求輸入的驗證碼是否正確。??

- public?class?JCaptcha?{??
- ????public?static?final?MyManageableImageCaptchaService?captchaService??
- ????????????=?new?MyManageableImageCaptchaService(new?FastHashMapCaptchaStore(),???
- ????????????????????????????new?GMailEngine(),?180,?100000,?75000);??
- ????public?static?boolean?validateResponse(??
- ????????HttpServletRequest?request,?String?userCaptchaResponse)?{??
- ????????if?(request.getSession(false)?==?null)?return?false;??
- ????????boolean?validated?=?false;??
- ????????try?{??
- ????????????String?id?=?request.getSession().getId();??
- ????????????validated?=???
- ????????????????captchaService.validateResponseForID(id,?userCaptchaResponse)??
- ????????????????????????????.booleanValue();??
- ????????}?catch?(CaptchaServiceException?e)?{??
- ????????????e.printStackTrace();??
- ????????}??
- ????????return?validated;??
- ????}???
- ????public?static?boolean?hasCaptcha(??
- ????????HttpServletRequest?request,?String?userCaptchaResponse)?{??
- ????????if?(request.getSession(false)?==?null)?return?false;??
- ????????boolean?validated?=?false;??
- ????????try?{??
- ????????????String?id?=?request.getSession().getId();??
- ????????????validated?=?captchaService.hasCapcha(id,?userCaptchaResponse);??
- ????????}?catch?(CaptchaServiceException?e)?{??
- ????????????e.printStackTrace();??
- ????????}??
- ????????return?validated;??
- ????}??
- }???
validateResponse():驗證當前請求輸入的驗證碼否正確;并從CaptchaService中刪除已經生成的驗證碼;
hasCaptcha():驗證當前請求輸入的驗證碼是否正確;但不從CaptchaService中刪除已經生成的驗證碼(比如Ajax驗證時可以使用,防止多次生成驗證碼);
?
五、JCaptchaFilter
用于生成驗證碼圖片的過濾器。??

- public?class?JCaptchaFilter?extends?OncePerRequestFilter?{??
- ????protected?void?doFilterInternal(HttpServletRequest?request,?HttpServletResponse?response,?FilterChain?filterChain)?throws?ServletException,?IOException?{??
- ??
- ????????response.setDateHeader("Expires",?0L);??
- ????????response.setHeader("Cache-Control",?"no-store,?no-cache,?must-revalidate");??
- ????????response.addHeader("Cache-Control",?"post-check=0,?pre-check=0");??
- ????????response.setHeader("Pragma",?"no-cache");??
- ????????response.setContentType("image/jpeg");??
- ????????String?id?=?request.getRequestedSessionId();??
- ????????BufferedImage?bi?=?JCaptcha.captchaService.getImageChallengeForID(id);??
- ????????ServletOutputStream?out?=?response.getOutputStream();??
- ????????ImageIO.write(bi,?"jpg",?out);??
- ????????try?{??
- ????????????out.flush();??
- ????????}?finally?{??
- ????????????out.close();??
- ????????}??
- ????}??
- }???
CaptchaService使用當前會話ID當作key獲取相應的驗證碼圖片;另外需要設置響應內容不進行瀏覽器端緩存。?
?

- <!--?驗證碼過濾器需要放到Shiro之后?因為Shiro將包裝HttpSession?如果不,可能造成兩次的sesison?id?不一樣?-->??
- <filter>??
- ??<filter-name>JCaptchaFilter</filter-name>??
- ??<filter-class>???
- ????com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaFilter??
- ??</filter-class>??
- ??</filter>??
- ??<filter-mapping>??
- ????<filter-name>JCaptchaFilter</filter-name>??
- ????<url-pattern>/jcaptcha.jpg</url-pattern>??
- </filter-mapping>???
這樣就可以在頁面使用/jcaptcha.jpg地址顯示驗證碼圖片。
?
六、JCaptchaValidateFilter
用于驗證碼驗證的Shiro過濾器。??

- public?class?JCaptchaValidateFilter?extends?AccessControlFilter?{??
- ????private?boolean?jcaptchaEbabled?=?true;//是否開啟驗證碼支持??
- ????private?String?jcaptchaParam?=?"jcaptchaCode";//前臺提交的驗證碼參數名??
- ????private?String?failureKeyAttribute?=?"shiroLoginFailure";?//驗證失敗后存儲到的屬性名??
- ????public?void?setJcaptchaEbabled(boolean?jcaptchaEbabled)?{??
- ????????this.jcaptchaEbabled?=?jcaptchaEbabled;??
- ????}??
- ????public?void?setJcaptchaParam(String?jcaptchaParam)?{??
- ????????this.jcaptchaParam?=?jcaptchaParam;??
- ????}??
- ????public?void?setFailureKeyAttribute(String?failureKeyAttribute)?{??
- ????????this.failureKeyAttribute?=?failureKeyAttribute;??
- ????}??
- ????protected?boolean?isAccessAllowed(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?throws?Exception?{??
- ????????//1、設置驗證碼是否開啟屬性,頁面可以根據該屬性來決定是否顯示驗證碼??
- ????????request.setAttribute("jcaptchaEbabled",?jcaptchaEbabled);??
- ??
- ????????HttpServletRequest?httpServletRequest?=?WebUtils.toHttp(request);??
- ????????//2、判斷驗證碼是否禁用?或不是表單提交(允許訪問)??
- ????????if?(jcaptchaEbabled?==?false?||?!"post".equalsIgnoreCase(httpServletRequest.getMethod()))?{??
- ????????????return?true;??
- ????????}??
- ????????//3、此時是表單提交,驗證驗證碼是否正確??
- ????????return?JCaptcha.validateResponse(httpServletRequest,?httpServletRequest.getParameter(jcaptchaParam));??
- ????}??
- ????protected?boolean?onAccessDenied(ServletRequest?request,?ServletResponse?response)?throws?Exception?{??
- ????????//如果驗證碼失敗了,存儲失敗key屬性??
- ????????request.setAttribute(failureKeyAttribute,?"jCaptcha.error");??
- ????????return?true;??
- ????}??
- }??
?
七、MyFormAuthenticationFilter
用于驗證碼驗證的Shiro攔截器在用于身份認證的攔截器之前運行;但是如果驗證碼驗證攔截器失敗了,就不需要進行身份認證攔截器流程了;所以需要修改下如FormAuthenticationFilter身份認證攔截器,當驗證碼驗證失敗時不再走身份認證攔截器。?

- public?class?MyFormAuthenticationFilter?extends?FormAuthenticationFilter?{??
- ????protected?boolean?onAccessDenied(ServletRequest?request,?ServletResponse?response,?Object?mappedValue)?throws?Exception?{??
- ????????if(request.getAttribute(getFailureKeyAttribute())?!=?null)?{??
- ????????????return?true;??
- ????????}??
- ????????return?super.onAccessDenied(request,?response,?mappedValue);??
- ????}??
- }???
即如果之前已經錯了,那直接跳過即可。
?
八、spring-config-shiro.xml? ? ???

- <!--?基于Form表單的身份驗證過濾器?-->??
- <bean?id="authcFilter"???
- ??class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.MyFormAuthenticationFilter">??
- ????<property?name="usernameParam"?value="username"/>??
- ????<property?name="passwordParam"?value="password"/>??
- ????<property?name="rememberMeParam"?value="rememberMe"/>??
- ????<property?name="failureKeyAttribute"?value="shiroLoginFailure"/>??
- </bean>??
- <bean?id="jCaptchaValidateFilter"???
- ??class="com.github.zhangkaitao.shiro.chapter22.jcaptcha.JCaptchaValidateFilter">??
- ????<property?name="jcaptchaEbabled"?value="true"/>??
- ????<property?name="jcaptchaParam"?value="jcaptchaCode"/>??
- ????<property?name="failureKeyAttribute"?value="shiroLoginFailure"/>??
- </bean>??
- <!--?Shiro的Web過濾器?-->??
- <bean?id="shiroFilter"?class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">??
- ????<property?name="securityManager"?ref="securityManager"/>??
- ????<property?name="loginUrl"?value="/login"/>??
- ????<property?name="filters">??
- ????????<util:map>??
- ????????????<entry?key="authc"?value-ref="authcFilter"/>??
- ????????????<entry?key="sysUser"?value-ref="sysUserFilter"/>??
- ????????????<entry?key="jCaptchaValidate"?value-ref="jCaptchaValidateFilter"/>??
- ????????</util:map>??
- ????</property>??
- ????<property?name="filterChainDefinitions">??
- ????????<value>??
- ????????????/static/**?=?anon??
- ????????????/jcaptcha*?=?anon??
- ????????????/login?=?jCaptchaValidate,authc??
- ????????????/logout?=?logout??
- ????????????/authenticated?=?authc??
- ????????????/**?=?user,sysUser??
- ????????</value>??
- ????</property>??
- </bean>??
?
九、login.jsp登錄頁面

- <c:if?test="${jcaptchaEbabled}">??
- ????驗證碼:??
- ????<input?type="text"?name="jcaptchaCode">??
- <img?class="jcaptcha-btn?jcaptcha-img"???
- src="${pageContext.request.contextPath}/jcaptcha.jpg"?title="點擊更換驗證碼">??
- ????<a?class="jcaptcha-btn"?href="javascript:;">換一張</a>??
- ????<br/>??
- </c:if>???
根據jcaptchaEbabled來顯示驗證碼圖片。
?
十、測試
輸入http://localhost:8080/chapter22將重定向到登錄頁面;輸入正確的用戶名/密碼/驗證碼即可成功登錄,如果輸入錯誤的驗證碼,將顯示驗證碼錯誤頁面:?
??
?
示例源代碼:https://github.com/zhangkaitao/shiro-example;
?
?
?
十一、另附講解:
先說明錯誤原因:
用<a href="http://lib.csdn.net/base/javaee" class="replace_word" title="Java EE知識庫" target="_blank" >spring</a>安全攔截器進行驗證碼的驗證的時候拋出異常。
throw new RuntimeException("captcha validation failed due to exception", cse);前臺提交數據后跳轉到如下方法:
?
?
- package?com.davidstudio.gbp.core.security.jcaptcha;??
- ??
- import?org.acegisecurity.captcha.CaptchaServiceProxy;??
- ??
- import?org.apache.log4j.Logger;??
- ??
- import?com.octo.captcha.service.CaptchaService;??
- import?com.octo.captcha.service.CaptchaServiceException;??
- ??
- /**?
- ?*?調用CaptchaService類,完jcaptcha的驗證過程?
- ?*??
- ?*?
- ?*??
- ?*?
- ?*/??
- public?class?JCaptchaServiceProxyImpl?implements?CaptchaServiceProxy?{??
- ??
- ????/**?
- ?????*?Logger?for?this?class?
- ?????*/??
- ????private?static?final?Logger?logger?=?Logger.getLogger(JCaptchaServiceProxyImpl.class);??
- ??
- ????private?CaptchaService?jcaptchaService;??
- ??
- ????public?boolean?validateReponseForId(String?id,?Object?response)?{??
- ????????if?(logger.isDebugEnabled())?{??
- ????????????logger.debug("validating?captcha?response");??
- ????????}??
- ???
- ????????try?{??
- ????????????boolean?isHuman?=?false;??
- ??????????????
- ????????????isHuman?=?jcaptchaService.validateResponseForID(id,?response).booleanValue();??
- ??????????????
- ????????????if?(isHuman)?{??
- ????????????????if?(logger.isDebugEnabled())?{??
- ????????????????????logger.debug("captcha?passed");??
- ????????????????}??
- ????????????}?else?{??
- ????????????????if?(logger.isDebugEnabled())?{??
- ????????????????????logger.debug("captcha?failed");??
- ????????????????}??
- ????????????}??
- ????????????return?isHuman;??
- ??????????????
- ????????}?catch?(CaptchaServiceException?cse)?{??
- ????????????//?fixes?known?bug?in?JCaptcha??
- ????????????logger.warn("captcha?validation?failed?due?to?exception",?cse);??
- ????????????throw?new?RuntimeException("captcha?validation?failed?due?to?exception",?cse);??
- ????????}??
- ????}??
- ??
- ????public?void?setJcaptchaService(CaptchaService?jcaptchaService)?{??
- ????????this.jcaptchaService?=?jcaptchaService;??
- ????}??
- } ?
?
?
?
?
?
設置斷點debug改語句不能順利執行?
?
- jcaptchaService.validateResponseForID(id,?response).booleanValue(); ?
?
?
查了網上的資料,這個方法的作用是: 根據HttpSession的 sessionId進行驗證碼的驗證,原理是這樣的,頁面生成的驗證碼是通過Spring中的配置生成的,查了一下配置:
?
- <bean?id="security.filter.manager"?class="org.acegisecurity.util.FilterChainProxy">??
- ????????<property?name="filterInvocationDefinitionSource">??
- ????????????<value>??
- ????????????????PATTERN_TYPE_APACHE_ANT??
- ????????????????/**=security.filter.channel,security.filter.sessionIntegration,security.filter.logout,security.filter.thsso,security.filter.jcaptcha,security.filter.jcaptchachannel,security.filter.formAuth,security.filter.requestWrap,security.filter.exceptionTranslation,security.filter.filterInvocation??
- ????????????</value>??
- ????????</property>??
- ????</bean>?
?
?
?
?
這是一個過濾器鏈,其中登錄的時候會進行如下過濾操作,
?
security.filter.channel,security.filter.sessionIntegration,security.filter.logout,security.filter.thsso,security.filter.jcaptcha,security.filter.jcaptchachannel,security.filter.formAuth,security.filter.requestWrap,security.filter.exceptionTranslation,security.filter.filterInvocation
一般配置的順序不能變,因為這是這些配置定義了用戶登錄的一套認證機制。
看了一下命名還算規范,其中涉及到驗證碼的過濾:security.filter.jcaptcha
查了一下這個驗證碼的引用配置:
?
- <!--?jcaptacha過濾器?-->??
- ????<bean?id="security.filter.jcaptcha"??
- ????????class="org.acegisecurity.captcha.CaptchaValidationProcessingFilter">??
- ????????<property?name="captchaService"?ref="security.captcha.serviceproxy"?/>??
- ????????<property?name="captchaValidationParameter"?value="j_captcha_response"?/>??
- ????</bean>??
- ????<bean?id="security.captcha.serviceproxy"??
- ????????class="com.davidstudio.gbp.core.security.jcaptcha.JCaptchaServiceProxyImpl">??
- ????????<property?name="jcaptchaService"?ref="security.captcha.service"?/>??
- ????</bean>??
- ????<bean?id="security.captcha.service"??
- ????????class="com.octo.captcha.service.image.DefaultManageableImageCaptchaService">??
- ????????<constructor-arg?type="com.octo.captcha.service.captchastore.CaptchaStore"?index="0">??
- ????????????<bean?class="com.octo.captcha.service.captchastore.FastHashMapCaptchaStore"?/>??
- ????????</constructor-arg>??
- ????????<constructor-arg?type="com.octo.captcha.engine.CaptchaEngine"?index="1">??
- ????????????<bean?class="com.davidstudio.gbp.core.security.jcaptcha.CaptchaEngine"?/>??
- ????????</constructor-arg>??
- ????????<constructor-arg?index="2">??
- ????????????<value>180</value>??
- ????????</constructor-arg>??
- ????????<constructor-arg?index="3">??
- ????????????<value>100000</value>??
- ????????</constructor-arg>??
- ????????<constructor-arg?index="4">??
- ????????????<value>75000</value>??
- ????????</constructor-arg>??
- ????</bean>??
?
?
?
通過bean配置反復引用。
?
剛開始以為SecurityContext沒有創建,查了一下配置也創建了:
?
?
- <!--??session整合過濾器。自動將用戶身份信息存放在session里。?-->??
- <bean?id="security.filter.sessionIntegration"??
- ????class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">??
- ????<property?name="context"?value="org.acegisecurity.captcha.CaptchaSecurityContextImpl"?/>??
- </bean>??
?
?
?
?copy
? 仔細看了一下這個方法的作用:
?
- jcaptchaService.validateResponseForID(id,?response).booleanValue(); ?
?
?
?
id就是httpSession的Id,response是從頁面獲得的輸入的驗證碼,當調用這個方法的時候,根據httpSession的id找到相應的驗證碼,如果有sessionId并且sessionId對應的驗證碼和輸入的驗證碼(這里就是response)一致的時候返回true,也就是用戶通過了驗證。
?
有一個疑問,驗證碼是怎么生成的?又怎么和httpSession進行綁定的?其實這套理論是可行的,當用戶第一次訪問頁面的時候會生成一個sessionId,頁面生成有驗證碼,關于驗證碼的生成,下面會進行介紹。就是畫一個圖片以留的方式顯示到頁面而已。用戶訪問的時候有一個對應的驗證碼和sessionId相對應。
如果驗證碼不清楚,點擊換一張,因為瀏覽器沒有關閉,sessionId依然是那個sessionId,只需要更新生成的驗證碼的值即可,這樣就做到了一個sessionId和一個驗證碼進行綁定了,這個過程在生成驗證碼的過程中就發生了。
如果用戶再次提交登錄信息,其中的sessionId沒有變,驗證碼是最新生成的驗證碼并且和sessionId進行了綁定,這樣就可以調用:
?
?
- jcaptchaService.validateResponseForID(id,?response).booleanValue();?這個條件進行驗證碼的驗證了,當然了驗證碼驗證前面還可以有很多過濾器認證,比如說對用戶名和密碼的驗證等等。形成一套的鏈式認證! ?
?
?
?
? ? ? 然而還有一個疑惑,這個sessionId是怎么和驗證碼進行綁定的呢?又是怎樣進行存儲的呢?
?我們看一下內存:
調用這段代碼的時候內存中有sessionId和response驗證碼的值:
下面是驗證碼生成的線程中內存的狀態:
由內存的狀態可以看出和配置文件是一致的,首先調用了com.davidstudio.gbp.core.security.jcaptcha.JCaptchaServiceProxyImpl
這個代理實現,這個代理實現類 又去調用com.octo.captcha.service.image.DefaultManageableImageCaptchaService
這個類才是生成驗證碼的類:查下spring這個類的源碼如下:
?
傳入的參數都有相應的說明,其中這個類繼承了AbstractManageableImageCaptchaService ?
?
繼續深入到這個類中看個究竟:
?
這個類中果然有我們想要的方法:
?
? 相應的通過store.getCaptcha(ID)通過這個ID獲得和這個sessionId匹配的驗證碼,再調用vilidateResponse方法進行驗證,如果和輸入的驗證碼相同就驗證通過了。
?
驗證通過后就把這個sessionId刪除了,如果你再次登錄,輸入驗證碼的時候是同一個邏輯,之所以刪除了這個ID我想是有好處的:
? ? ? ? ? ?原因如下,如果不進行刪除,隨著的登錄訪問用戶的過多,hashMap中的值會越來越多,這樣以后再進行驗證的時候速度和效率都會受到印象,如果刪除了這個sessionId,這樣這個store中的hashMap只是存儲了當前正在準備登錄的sessionId和相應的驗證碼!這樣效率就大大提高了,如果有10萬個人同時登錄,都不是問題!
? ? ? ?通過這個方法的調用我們就知道了sessionId是怎么和驗證碼綁定存儲在hashMap中的!讓我們進入源碼驗證一下:
?
?
上面就是CaptchaStore接口的實現類MapCaptchaStore,其中定義了一個hashMap,通過storeCaptcha(String id,Captcha captcha)方法來存儲sessionId和captcha的鍵值對,這是進入登錄頁面生成的時候調用的方法,當進行驗證的時候就需要hasCaptcha(String ID)方法和
?
但是我們是調用了
- MapCaptchaStore?的子類FastHashMapCaptchaStore來存儲信息的:同樣看FastHashMapCaptchaStore這個類:
- ?
- ? ?17???public?class?FastHashMapCaptchaStore?extends?MapCaptchaStore?{??
- ???18???????public?FastHashMapCaptchaStore()?{??
- ???19???????????this.store?=?new?FastHashMap();??
- ???20???????}??
- ???21???}
- ?
- 這就是這個類的全部了,再看一下FastHashMap類:??
- ?
- public?class?FastHashMap?extends?HashMap?{??
- ???67?????
- ???68???????/**?
- ???69????????*?The?underlying?map?we?are?managing.?
- ???70????????*/??
- ???71???????protected?HashMap?map?=?null;??
- ???72?????
- ???73???????/**?
- ???74????????*?Are?we?currently?operating?in?"fast"?mode??
- ???75????????*/??
- ???76???????protected?boolean?fast?=?false;??
- ???77?????
- ???78???????//?Constructors??
- ???79???????//?----------------------------------------------------------------------??
- ???80?????
- ???81???????/**?
- ???82????????*?Construct?an?empty?map.?
- ???83????????*/??
- ???84???????public?FastHashMap()?{??
- ???85???????????super();??
- ???86???????????this.map?=?new?HashMap();??
- ???87???????}??
- ???
- 這個類是HashMap的一個擴展,里面有兩種方式操作,一種是快速的不同步,一種是同步的操作!??
- ?
- 顯然FastHashMapCaptchaStore就是一個HashMap。驗證碼的實現在這個類中:
- ?
- ? ?18????*?Base?implementation?of?the?ImageCaptchaService.??
- ???19????*??
- ???20????*?@author?<a?href="mailto:mag@jcaptcha.net">Marc-Antoine?Garrigue</a>??
- ???21????*?@version?1.0??
- ???22????*/??
- ???23???public?abstract?class?AbstractManageableImageCaptchaService?extends?AbstractManageableCaptchaService??
- ???24???????????implements?ImageCaptchaService?{??
- ???25?????
- ???26???????protected?AbstractManageableImageCaptchaService(CaptchaStore?captchaStore,??
- ???27???????????????????????????????????????????????????????com.octo.captcha.engine.CaptchaEngine?captchaEngine,??
- ???28???????????????????????????????????????????????????????int?minGuarantedStorageDelayInSeconds,??
- ???29???????????????????????????????????????????????????????int?maxCaptchaStoreSize,??
- ???30???????????????????????????????????????????????????????int?captchaStoreLoadBeforeGarbageCollection)?{??
- ???31???????????super(captchaStore,?captchaEngine,??
- ???32???????????????????minGuarantedStorageDelayInSeconds,?maxCaptchaStoreSize,??
- ???33???????????????????captchaStoreLoadBeforeGarbageCollection);??
- ???34???????}
- ?
- ?
- ? ?73???????protected?Object?getChallengeClone(Captcha?captcha)?{ ?
- ???74???????????BufferedImage?challenge?=?(BufferedImage)?captcha.getChallenge();??
- ???75???????????BufferedImage?clone?=?new?BufferedImage(challenge.getWidth(),?challenge.getHeight(),?challenge.getType());??
- ???76?????
- ???77???????????clone.getGraphics().drawImage(challenge,?0,?0,?clone.getWidth(),?clone.getHeight(),?null);??
- ???78???????????clone.getGraphics().dispose();??
- ???79?????
- ???80?????
- ???81???????????return?clone;??
- ???82???????}
- ?
- 在這個類中,只是定義了一種,Captcha也是一種接口。??
- 可以到內存中看一看有木有那個hashMap
- <span?style="white-space:pre"><img?src="https://img-my.csdn.net/uploads/201211/23/1353676134_4969.png"?alt="">????</span>
- ?
- 內存中清楚顯示了hashTable中的key和value,這樣就證明驗證碼生成成功。但是為什么每次驗證都是報錯呢?
- 后來無奈看了看發送到?sessionId在hashMap中是否有,結果是不一樣,就是再hashMap中沒有,為什么?不是每一次在驗證碼生成的時候都把sessionId放進去了嗎?為什么會不一樣呢?
- ?
- 原因其實很簡單,就是當點擊登陸的時候服務器又給分配了一個sessionId,這樣就和以前的sessionId不一樣了,在hashMap中就找不到對應的驗證碼了。
- 原則上講服務器在第一次訪問的時候會給用戶分配一個不重復的sessionId,如果服務器的session不超時就不會再給用戶分配sessionId了,減少給服務器的壓力,也帶來了友好的體驗。但是我的兩次sessiId為什么不一樣呢?
- ?
- ?后來通過fiddler2這個軟件(這個軟件好強大可以獲得發送到form表單的內容,甚至可以修改),可以看到本地存儲的cookie,但是cookie是空的,就是nodata,汗啊,難怪每次都分配不同的sessionId,服務器怎么判斷每次提交過去的是同一個用戶呢?
- ?
- 通過sessionId,服務器會在客戶端把sessionId寫在Cookie中,這樣用戶再次提交請求的時候,服務器如果在內存中找到用戶cookie中的sessionId而且沒有超時,就不再重新分配sessionId,我看了下IE瀏覽器,cookie被禁止了,難怪每次都是一個新的sessionId,驗證碼就無法驗證。就報錯了。
- 學習中應該多去看源碼,分析源碼設計理念。
?