IT忍者神龟之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>
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>

仔细看了一下这个方法的作用:

 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这个类的源码如下:

23   public class DefaultManageableImageCaptchaService extends AbstractManageableImageCaptchaService
   24           implements ImageCaptchaService {
   25       /**
   26        * Construct a new ImageCaptchaService with a {@link FastHashMapCaptchaStore} and a {@link DefaultGimpyEngine}
   27        *  minGuarantedStorageDelayInSeconds = 180s
   28        *  maxCaptchaStoreSize = 100000
   29        *  captchaStoreLoadBeforeGarbageCollection=75000
   30        */
   31       public DefaultManageableImageCaptchaService() {
   32           super(new FastHashMapCaptchaStore(), new DefaultGimpyEngine(), 180,
   33                   100000, 75000);
   34       }

传入的参数都有相应的说明,其中这个类继承了

AbstractManageableImageCaptchaService

继续深入到这个类中看个究竟:

这个类中果然有我们想要的方法:

  127       /**
  128        * Method to validate a response to the challenge corresponding to the given ticket and remove the coresponding
  129        * captcha from the store.
  130        *
  131        * @param ID the ticket provided by the buildCaptchaAndGetID method
  132        * @return true if the response is correct, false otherwise.
  133        * @throws CaptchaServiceException if the ticket is invalid
  134        */
  135       public Boolean validateResponseForID(String ID, Object response)
  136               throws CaptchaServiceException {
  137           if (!store.hasCaptcha(ID)) {
  138               throw new CaptchaServiceException("Invalid ID, could not validate unexisting or already validated captcha");
  139           } else {
  140               Boolean valid = store.getCaptcha(ID).validateResponse(response);
  141               store.removeCaptcha(ID);
  142               return valid;
  143           }
  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中的!让我们进入源码验证一下:

  18   /**
   19    * Simple store based on a HashMap
   20    */
   21   public class MapCaptchaStore implements CaptchaStore {
   22
   23       Map store;
   24
   25       public MapCaptchaStore() {
   26           this.store = new HashMap();
   27       }
   28
   29       /**
   30        * Check if a captcha is stored for this id
   31        *
   32        * @return true if a captcha for this id is stored, false otherwise
   33        */
   34       public boolean hasCaptcha(String id) {
   35           return store.containsKey(id);
   36       }
   37
   38       /**
   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
   40        * to store a captcha, the store will return an exception
   41        *
   42        * @param id      the key
   43        * @param captcha the captcha
   44        *
   45        * @throws CaptchaServiceException if the captcha already exists, or if an error occurs during storing routine.
   46        */
   47       public void storeCaptcha(String id, Captcha captcha) throws CaptchaServiceException {
   48   //        if (store.get(id) != null) {
   49   //            throw new CaptchaServiceException("a captcha with this id already exist. This error must " +
   50   //                    "not occurs, this is an implementation pb!");
   51   //        }
   52           store.put(id, new CaptchaAndLocale(captcha));
   53       }

上面就是CaptchaStore接口的实现类MapCaptchaStore,其中定义了一个hashMap,通过storeCaptcha(String id,Captcha captcha)方法来存储sessionId和captcha的键值对,这是进入登录页面生成的时候调用的方法,当进行验证的时候就需要hasCaptcha(String ID)方法和

 69       /**
   70        * Retrieve the captcha for this key from the store.
   71        *
   72        * @return the captcha for this id
   73        *
   74        * @throws CaptchaServiceException if a captcha for this key is not found or if an error occurs during retrieving
   75        *                                 routine.
   76        */
   77       public Captcha getCaptcha(String id) throws CaptchaServiceException {
   78           Object captchaAndLocale = store.get(id);
   79           return captchaAndLocale!=null?((CaptchaAndLocale) captchaAndLocale).getCaptcha():null;
   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这个类:
<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">  17   public class FastHashMapCaptchaStore extends MapCaptchaStore {
   18       public FastHashMapCaptchaStore() {
   19           this.store = new FastHashMap();
   20       }
   21   }

这就是这个类的全部了,再看一下FastHashMap类:





<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">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       }
   88   

这个类是HashMap的一个扩展,里面有两种方式操作,一种是快速的不同步,一种是同步的操作!


显然<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;">FastHashMapCaptchaStore就是一个HashMap
验证码的实现在这个类中:
<pre name="code" class="java" style="white-space: pre-wrap; word-wrap: break-word;"> 18    * Base implementation of the ImageCaptchaService.
   19    *
   20    * @author <a href="mailto:[email protected]">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="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;">





				
时间: 2024-11-09 22:35:06

IT忍者神龟之com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting o的相关文章

每日一大片之忍者神龟变种时代

忍者神龟变种时代BD 主演:梅根·福克斯  阿伦·瑞奇森  诺尔·费舍  皮特·普劳泽克  杰瑞米·霍华德  威尔·阿奈特   类型:动作片 导演:乔纳森·理贝斯曼   地区:美国 年份:2014 语言: 介 绍:繁华大都会美国纽约,邪恶领袖施莱德(水源士郎 饰)率领的大脚帮在城市内为非作歹,甚嚣尘上.安保公司老板萨克斯(威廉·菲德内尔 饰)推出最新安保系统,发誓维护城市的和平.第6频道女记者爱普尔·奥尼尔(梅根·福克斯 饰)偶然拍到大脚帮被四名神秘侠客挫败的画面,在第二次遭遇非常事件时,她惊讶

IT忍者神龟之mysql远程连接:ERROR 1130 (HY000): Host &#39;*.*.*.*&#39; is not allowed to connect to this MySQL server解决

安装完MySQL后,远程连接数据库的时候,出现 ERROR 1130 (HY000): Host '192.168.0.1' is not allowed to connect to this MySQL server提示信息,不能远程连接数据库.考虑可能是因为系统数据库mysql中user表中的host是localhost的原因,于是,我尝试把这个值改为自己服务器的ip,果然就好用了,不过用 mysql -u root -p命令就连不上数据库了,需要用mysql -h 服务器ip -u roo

IT忍者神龟之Database Link详解

-创建 CREATE public database link test_link CONNECT TO scott IDENTIFIED BY tiger using '(DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521)) ) (CONNECT_DATA = (SERVICE_NAME = LEE) ) )'; --使用 select ename from [emai

IT忍者神龟之 配额不足的解决方法ORA-01536: space quota exceeded for tablespace

今天有同事反映最近几天的数据在oracle中查不到.检查TT的错误日志显示:TT5211: TT5211: Oracle out of resource error in OCIStmtExecute(): ORA-01536: space quota exceeded for tablespace 'TBSLOG' rc = -1 -- file "bdbTblH.c", lineno 2452, procedure "ttBDbStmtForce()". 明显的

IT忍者神龟之oracle 中一个用户怎么可以不使用用户名访问其他用户的表

故事背景是这样的:一个项目大概涉及到4个工程同时开发,在我自己的工程中需要做一个报表,但是要访问另一个工程所连接的DB,当然两个工程的DB Server是在同一个IP上,也就是说我们之间只是schema不一样,那我怎样完成下面的报表的开发呢? 一开始我直接创建了一个DBLink,但是回头和同时沟通,他认为DBLink的影响过大,当两个数据库不在同一个服务器上的时候才会用到这个,当前的情况最好用schema,但是此处还是把创建DBLink的方法写出来方便记忆: create database li

IT忍者神龟之如何批量删除Win7旗舰版系统下的.svn文件

在使用SVN工具的时候会生成一些以"svn"作为后缀的文件,而且每个文件夹下都有,数量很多的. 如果想删除Win7旗舰版系统下的.svn文件夹,通过手动删除的渠道是最麻烦的,因为每个文件夹下面都存在这样的文件. 在记事本输入以下代码并命名为以.reg作为扩展名的文件: Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN] @="Dele

IT忍者神龟之命令行执行java程序

第一:简单的命令行(没有package) c:/temp/bin/GetGreeting.java [java] view plaincopy public class GetGreeting { public static void main(String [] args) { System.out.println("Hello world"); } } 1.编译:cmd--cd到c:/temp/bin  javac GetGreeting.java 生成GetGreeting.cl

IT忍者神龟之LDAP 入门知识

如果你刚接触ldap,你一定看了很多ldap相关的教程,看过很多教程.都不是很好,唯独这一份写得最好. dn:一条记录的位置 dc:一条记录所属区域 ou:一条记录所属组织 cn/uid:一条记录的名字/ID 实际上更多时候我只把它看成数据库.我把它和我非常熟悉的MYSQL数据库做比较,通常会得到更好的理解: MYSQL用"表"储存数据,LDAP用"树" MYSQL指定一条记录要3个条件:DB.TABLE.ROW. LDAP却更自由,为什么呢?因为LDAP数据是&q

IT忍者神龟之数据库备份还原技术总结

1. exp/imp (导出与导入装库与卸库) 1.1 基本命令 1. 获取帮助 $ exp help=y $ imp help=y 2. 三种工作方式 (1)交互式方式 $ exp // 然后按提示输入所需要的参数 (2)命令行方式 $ exp user/[email protected] file=/oracle/test.dmp full=y // 命令行中输入所需的参数 (3)参数文件方式 $ exp parfile=username.par // 在参数文件中输入所需的参数 参数文件