• 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- 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;">
    
    
    
    
    
    
    
    
    
    
    
                
  • 相关阅读:
    JS的编码、解码及C#中对应的解码、编码 itprobie
    word、excel、ppt转换成html itprobie
    js 导出到word,excel itprobie
    word、excel、ppt转换成pdf itprobie
    SELECT INTO 和 INSERT INTO SELECT 两种表复制语句
    Copy Table From Another Table
    系统表相关SQL语句
    sp_executesql Demo
    SQLServer2000删除重复数据
    SQL Tran Save Point
  • 原文地址:https://www.cnblogs.com/jpfss/p/9487604.html
Copyright © 2020-2023  润新知