com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting o


<p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;">先說明錯誤原因:用spring安全攔截器進行驗證碼的驗證的時候拋出異常。</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;">throw new RuntimeException("captcha validation failed due to exception", cse);</p><p style="margin-top: 0px; margin-bottom: 0px; padding-top: 0px; padding-bottom: 0px;">前台提交數據后跳轉到如下方法:</p>

  
  
 
 
         

   
   
  
  
          
  1. package com.davidstudio.gbp.core.security.jcaptcha;
  2. import org.acegisecurity.captcha.CaptchaServiceProxy;
  3. import org.apache.log4j.Logger;
  4. import com.octo.captcha.service.CaptchaService;
  5. import com.octo.captcha.service.CaptchaServiceException;
  6. /**
  7. * 調用CaptchaService類,完jcaptcha的驗證過程
  8. *
  9. *
  10. *
  11. *
  12. */
  13. public class JCaptchaServiceProxyImpl implements CaptchaServiceProxy {
  14. /**
  15. * Logger for this class
  16. */
  17. private static final Logger logger = Logger.getLogger(JCaptchaServiceProxyImpl.class);
  18. private CaptchaService jcaptchaService;
  19. public boolean validateReponseForId(String id, Object response) {
  20. if (logger.isDebugEnabled()) {
  21. logger.debug( "validating captcha response");
  22. }
  23. try {
  24. boolean isHuman = false;
  25. isHuman = jcaptchaService.validateResponseForID(id, response).booleanValue();
  26. if (isHuman) {
  27. if (logger.isDebugEnabled()) {
  28. logger.debug( "captcha passed");
  29. }
  30. } else {
  31. if (logger.isDebugEnabled()) {
  32. logger.debug( "captcha failed");
  33. }
  34. }
  35. return isHuman;
  36. } catch (CaptchaServiceException cse) {
  37. // fixes known bug in JCaptcha
  38. logger.warn( "captcha validation failed due to exception", cse);
  39. throw new RuntimeException( "captcha validation failed due to exception", cse);
  40. }
  41. }
  42. public void setJcaptchaService(CaptchaService jcaptchaService) {
  43. this.jcaptchaService = jcaptchaService;
  44. }
  45. }
設置斷點debug改語句不能順利執行 
 jcaptchaService.validateResponseForID(id, response).booleanValue();
  
  
 
 
         

查了網上的資料,這個方法的作用是: 根據HttpSession的 sessionId進行驗證碼的驗證,原理是這樣的,頁面生成的驗證碼是通過Spring中的配置生成的,查了一下配置:


   
   
  
  
          
  1. <bean id="security.filter.manager" class="org.acegisecurity.util.FilterChainProxy">
  2. <property name="filterInvocationDefinitionSource">
  3. <value>
  4. PATTERN_TYPE_APACHE_ANT
  5. /**=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
  6. </value>
  7. </property>
  8. </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

查了一下這個驗證碼的引用配置:


   
   
  
  
          
  1. <!-- jcaptacha過濾器 -->
  2. <bean id="security.filter.jcaptcha"
  3. class= "org.acegisecurity.captcha.CaptchaValidationProcessingFilter">
  4. <property name="captchaService" ref="security.captcha.serviceproxy" />
  5. <property name="captchaValidationParameter" value="j_captcha_response" />
  6. </bean>
  7. <bean id="security.captcha.serviceproxy"
  8. class= "com.davidstudio.gbp.core.security.jcaptcha.JCaptchaServiceProxyImpl">
  9. <property name="jcaptchaService" ref="security.captcha.service" />
  10. </bean>
  11. <bean id="security.captcha.service"
  12. class= "com.octo.captcha.service.image.DefaultManageableImageCaptchaService">
  13. <constructor-arg type="com.octo.captcha.service.captchastore.CaptchaStore" index="0">
  14. <bean class="com.octo.captcha.service.captchastore.FastHashMapCaptchaStore" />
  15. </constructor-arg>
  16. <constructor-arg type="com.octo.captcha.engine.CaptchaEngine" index="1">
  17. <bean class="com.davidstudio.gbp.core.security.jcaptcha.CaptchaEngine" />
  18. </constructor-arg>
  19. <constructor-arg index="2">
  20. <value>180 </value>
  21. </constructor-arg>
  22. <constructor-arg index="3">
  23. <value>100000 </value>
  24. </constructor-arg>
  25. <constructor-arg index="4">
  26. <value>75000 </value>
  27. </constructor-arg>
  28. </bean>

通過bean配置反復引用。

剛開始以為SecurityContext沒有創建,查了一下配置也創建了:


   
   
  
  
          
  1. <!-- session整合過濾器。自動將用戶身份信息存放在session里。 -->
  2. <bean id="security.filter.sessionIntegration"
  3. class= "org.acegisecurity.context.HttpSessionContextIntegrationFilter">
  4. <property name="context" value="org.acegisecurity.captcha.CaptchaSecurityContextImpl" />
  5. </bean>

  仔細看了一下這個方法的作用:
 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這個類的源碼如下:


   
   
  
  
          
  1. 23 public class DefaultManageableImageCaptchaService extends AbstractManageableImageCaptchaService
  2. 24 implements ImageCaptchaService {
  3. 25 /**
  4. 26 * Construct a new ImageCaptchaService with a {@link FastHashMapCaptchaStore} and a {@link DefaultGimpyEngine}
  5. 27 * minGuarantedStorageDelayInSeconds = 180s
  6. 28 * maxCaptchaStoreSize = 100000
  7. 29 * captchaStoreLoadBeforeGarbageCollection=75000
  8. 30 */
  9. 31 public DefaultManageableImageCaptchaService() {
  10. 32 super( new FastHashMapCaptchaStore(), new DefaultGimpyEngine(), 180,
  11. 33 100000, 75000);
  12. 34 }

傳入的參數都有相應的說明,其中這個類繼承了
AbstractManageableImageCaptchaService
  
  
 
 
         
繼續深入到這個類中看個究竟:

這個類中果然有我們想要的方法:


   
   
  
  
          
  1. 127 /**
  2. 128 * Method to validate a response to the challenge corresponding to the given ticket and remove the coresponding
  3. 129 * captcha from the store.
  4. 130 *
  5. 131 * @param ID the ticket provided by the buildCaptchaAndGetID method
  6. 132 * @return true if the response is correct, false otherwise.
  7. 133 * @throws CaptchaServiceException if the ticket is invalid
  8. 134 */
  9. 135 public Boolean validateResponseForID(String ID, Object response)
  10. 136 throws CaptchaServiceException {
  11. 137 if (!store.hasCaptcha(ID)) {
  12. 138 throw new CaptchaServiceException( "Invalid ID, could not validate unexisting or already validated captcha");
  13. 139 } else {
  14. 140 Boolean valid = store.getCaptcha(ID).validateResponse(response);
  15. 141 store.removeCaptcha(ID);
  16. 142 return valid;
  17. 143 }
  18. 144 }
這個就是判斷有沒有驗證碼,如果store中沒有相應的sessionId那么就拋出異常:
throw new CaptchaServiceException("Invalid ID, could not validate unexisting or already validated captcha");
  
  
 
 
         
根本就沒有這個sessionId,如果有這個sessionId就走了另一個邏輯:

 Boolean valid = store.getCaptcha(ID).validateResponse(response);
  
  
 
 
         
           相應的通過store.getCaptcha(ID)通過這個ID獲得和這個sessionId匹配的驗證碼,再調用vilidateResponse方法進行驗證,如果和輸入的驗證碼相同就驗證通過了。

驗證通過后就把這個sessionId刪除了,如果你再次登錄,輸入驗證碼的時候是同一個邏輯,之所以刪除了這個ID我想是有好處的:

           原因如下,如果不進行刪除,隨着的登錄訪問用戶的過多,hashMap中的值會越來越多,這樣以后再進行驗證的時候速度和效率都會受到印象,如果刪除了這個sessionId,這樣這個store中的hashMap只是存儲了當前正在准備登錄的sessionId和相應的驗證碼!這樣效率就大大提高了,如果有10萬個人同時登錄,都不是問題!

       通過這個方法的調用我們就知道了sessionId是怎么和驗證碼綁定存儲在hashMap中的!讓我們進入源碼驗證一下:


   
   
  
  
          
  1. 18 /**
  2. 19 * Simple store based on a HashMap
  3. 20 */
  4. 21 public class MapCaptchaStore implements CaptchaStore {
  5. 22
  6. 23 Map store;
  7. 24
  8. 25 public MapCaptchaStore() {
  9. 26 this.store = new HashMap();
  10. 27 }
  11. 28
  12. 29 /**
  13. 30 * Check if a captcha is stored for this id
  14. 31 *
  15. 32 * @return true if a captcha for this id is stored, false otherwise
  16. 33 */
  17. 34 public boolean hasCaptcha(String id) {
  18. 35 return store.containsKey(id);
  19. 36 }
  20. 37
  21. 38 /**
  22. 39 * Store the captcha with the provided id as key. The key is assumed to be unique, so if the same key is used twice
  23. 40 * to store a captcha, the store will return an exception
  24. 41 *
  25. 42 * @param id the key
  26. 43 * @param captcha the captcha
  27. 44 *
  28. 45 * @throws CaptchaServiceException if the captcha already exists, or if an error occurs during storing routine.
  29. 46 */
  30. 47 public void storeCaptcha(String id, Captcha captcha) throws CaptchaServiceException {
  31. 48 // if (store.get(id) != null) {
  32. 49 // throw new CaptchaServiceException("a captcha with this id already exist. This error must " +
  33. 50 // "not occurs, this is an implementation pb!");
  34. 51 // }
  35. 52 store.put(id, new CaptchaAndLocale(captcha));
  36. 53 }

上面就是CaptchaStore接口的實現類MapCaptchaStore,其中定義了一個hashMap,通過storeCaptcha(String id,Captcha captcha)方法來存儲sessionId和captcha的鍵值對,這是進入登錄頁面生成的時候調用的方法,當進行驗證的時候就需要hasCaptcha(String ID)方法和


   
   
  
  
          
  1. 69 /**
  2. 70 * Retrieve the captcha for this key from the store.
  3. 71 *
  4. 72 * @return the captcha for this id
  5. 73 *
  6. 74 * @throws CaptchaServiceException if a captcha for this key is not found or if an error occurs during retrieving
  7. 75 * routine.
  8. 76 */
  9. 77 public Captcha getCaptcha(String id) throws CaptchaServiceException {
  10. 78 Object captchaAndLocale = store.get(id);
  11. 79 return captchaAndLocale!= null?((CaptchaAndLocale) captchaAndLocale).getCaptcha(): null;
  12. 80 }
但是我們是調用了
MapCaptchaStore 的子類<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">FastHashMapCaptchaStore來存儲信息的:同樣看<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">FastHashMapCaptchaStore這個類:
  
  
 
 
         

   
   
  
  
          
  1. <pre name= "code" class= "java" style= "white-space: pre-wrap; word-wrap: break-word;"> 17 public class FastHashMapCaptchaStore extends MapCaptchaStore {
  2. 18 public FastHashMapCaptchaStore() {
  3. 19 this.store = new FastHashMap();
  4. 20 }
  5. 21 }
這就是這個類的全部了,再看一下FastHashMap類:
 
        
 
        
 
        

 
        

   
   
  
  
          
  1. <pre name= "code" class= "java" style= "white-space: pre-wrap; word-wrap: break-word;"> public class FastHashMap extends HashMap {
  2. 67
  3. 68 /**
  4. 69 * The underlying map we are managing.
  5. 70 */
  6. 71 protected HashMap map = null;
  7. 72
  8. 73 /**
  9. 74 * Are we currently operating in "fast" mode?
  10. 75 */
  11. 76 protected boolean fast = false;
  12. 77
  13. 78 // Constructors
  14. 79 // ----------------------------------------------------------------------
  15. 80
  16. 81 /**
  17. 82 * Construct an empty map.
  18. 83 */
  19. 84 public FastHashMap() {
  20. 85 super();
  21. 86 this.map = new HashMap();
  22. 87 }
  23. 88

這個類是HashMap的一個擴展,里面有兩種方式操作,一種是快速的不同步,一種是同步的操作!
 
        
顯然<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">FastHashMapCaptchaStore就是一個HashMap
  
  
 
 
         
驗證碼的實現在這個類中:
  
  
 
 
         

   
   
  
  
          
  1. <pre name= "code" class= "java" style= "white-space: pre-wrap; word-wrap: break-word;"> 18 * Base implementation of the ImageCaptchaService.
  2. 19 *
  3. 20 * @author <a href= "mailto:mag@jcaptcha.net">Marc-Antoine Garrigue</a>
  4. 21 * @version 1.0
  5. 22 */
  6. 23 public abstract class AbstractManageableImageCaptchaService extends AbstractManageableCaptchaService
  7. 24 implements ImageCaptchaService {
  8. 25
  9. 26 protected AbstractManageableImageCaptchaService(CaptchaStore captchaStore,
  10. 27 com.octo.captcha.engine.CaptchaEngine captchaEngine,
  11. 28 int minGuarantedStorageDelayInSeconds,
  12. 29 int maxCaptchaStoreSize,
  13. 30 int captchaStoreLoadBeforeGarbageCollection) {
  14. 31 super(captchaStore, captchaEngine,
  15. 32 minGuarantedStorageDelayInSeconds, maxCaptchaStoreSize,
  16. 33 captchaStoreLoadBeforeGarbageCollection);
  17. 34 }



   
   
  
  
          
  1. 73 protected Object getChallengeClone(Captcha captcha) {
  2. 74 BufferedImage challenge = (BufferedImage) captcha.getChallenge();
  3. 75 BufferedImage clone = new BufferedImage(challenge.getWidth(), challenge.getHeight(), challenge.getType());
  4. 76
  5. 77 clone.getGraphics().drawImage(challenge, 0, 0, clone.getWidth(), clone.getHeight(), null);
  6. 78 clone.getGraphics().dispose();
  7. 79
  8. 80
  9. 81 return clone;
  10. 82 }
在這個類中,只是定義了一種,Captcha也是一種接口。
 
        
可以到內存中看一看有木有那個hashMap
  
  
 
 
         
<span style="margin: 0px; padding: 0px; white-space: pre;"><img src="http://img.my.csdn.net/uploads/201211/23/1353676134_4969.png" alt="" style="border: none; max-width: 100%;" />	</span>
  
  
 
 
         
</pre><pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">內存中清楚顯示了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,驗證碼就無法驗證。就報錯了。
  
  
 
 
         
      學習中應該多去看源碼,分析源碼設計理念。最好的參考就是源碼了,要養成多看源碼的習慣,即使有的看不懂。我暈寫的太長了。
  
  
 
 
         

</pre><pre style="white-space: pre-wrap; word-wrap: break-word;">
  
  
 
 
         
 
        
 
        
 
        
 
        
 
        
 
       


免責聲明!

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



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