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: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="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;">
com.octo.captcha.service.CaptchaServiceException: Invalid ID, could not validate unexisting o的更多相关文章
- jcaptcha组件小小改造解决Invalid ID, could not validate une
https://my.oschina.net/chainlong/blog/192014
- linux环境,通过rpm删除mysql包,报错:error reading information on service mysqld: Invalid argument
问题描述: 今天在做saltstack的练习,想要通过sls的方式,在远程进行mysql数据库的安装,发现无法通过service的方式启动数据库,然后就想给删除了重新进行安装,在通过rpm -e进行删 ...
- JCaptcha做验证码遇到的问题引出的思考
JCaptcha用来做用户登录时期的验证码的,但是今天将开放的应用系统部署到生产环境的时候,遇到了问题,总是提示验证码不对.后台报出来下面的错误: com.octo.captcha.service.C ...
- jcaptcha和kaptcha验证码使用入门【转】
jcaptcha和kaptcha验证码使用入门 一.jcaptcha验证码使用 jcaptcha使用默认样式生成的验证码比较难以识别,所以需要自定义验证码的样式,包括,背景色.背景大小.字体.字体大小 ...
- java验证码组件kaptcha使用方法
使用方法: 项目中导入kaptcha-2.3.jar包在web.xml里面新增: <!-- 登陆验证码Kaptcha --> <servlet> <s ...
- Liferay 6.1开发学习
http://www.huqiwen.com/2013/01/10/liferay-6-1-development-study-17-springmvc-portlet/ http://www.blo ...
- 验证码生成组件--JCaptcha的使用
以下为项目中所用代码,文笔有限,直接上代码. 所需jar包: 是否需要其他依赖包,不详 web.xml <servlet> <servlet-name>Jcaptcha< ...
- 使用jcaptcha插件生成验证码
1.从官网http://jcaptcha.sourceforge.net/下载插件.将对应jar包导入到lib文件夹下 2.创建一个CaptchaServiceSingleton类用来获取jcaptc ...
- captche验证码
JCaptcha 是一个用来生成验证码的开源Java类库 CaptchaServiceSingleton类(单态类) package com.dongbin.testy; import com.oct ...
随机推荐
- Spring中事务的XML方式[声明方式]
事务管理: 管理事务,管理数据,数据完整性和一致性 事务[业务逻辑] : 由一系列的动作[查询书价格,更新库存,更新余额],组成一个单元[买书业务], 当我们动作当中有一个错了,全错~ ACID 原子 ...
- 洛谷P3613 睡觉困难综合征(LCT)
题目: P3613 睡觉困难综合症 解题思路: LCT,主要是维护链上的多位贪心答案,推个公式:分类讨论入0/1的情况,合并就好了(公式是合并用的) 代码(我不知道之前那个为啥一直wa,改成结构体就好 ...
- vmware workstation安装windows server 2019
提示需要输入密钥,选择[我没有密钥] 选择有桌面体验的 自定义 新建C盘 100G 选择100G的主分区,下一步 PS:未分配的空间,先不理,安装完系统,进桌面,再分配 开始安装.接下来就是等. 等. ...
- 词向量 word2vec
看的这一篇的笔记 http://licstar.net/archives/328 看不太懂. 要学的话,看这里吧,这里把一些资料做了整合: http://www.cnblogs.com/wuzhitj ...
- 想知道WiFi是什么样子的么?
据英国<每日邮报>报道.英国纽卡斯尔大学博士生路易斯·赫南日前利用定制的仪器为WiFi信号拍照,绘制出一系列展现人类周围无形网络WiFi连接情况的图,这些盘旋环绕的明亮光束,宛如幽灵一般缠 ...
- progerssbar-style 属性分析
先看如下代码 <ProgressBar android:id="@+id/stateProgressBar" android:orientation="horizo ...
- Android时间戳与字符串相互转换
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public cl ...
- 修饰器&高阶组件
一.修饰器 1.类的修饰 修饰器是一个函数,用来修改类的行为 function testable(target) { target.isTestable = true; } @testable cla ...
- 【例题 6-20 UVA - 1599】Ideal Path
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 逆向做一遍bfs. 得到终点到某个点的最短距离. 这样,我们从起点顺序的时候. 就能知道最短路的下一步是要走哪里了. 这样,我们从起 ...
- java与javax的区别分析
Java是一种受C语言影响的编程语言.Java和Javax本质上是与Java编程语言的上下文一起使用的包.实际上Java和Javax没有区别.这只是不同的名字. Java是一种编程语言,受到C语言的影 ...