运用Jcaptcha做验证码总结
转自:
http://my.oschina.net/jawava/blog/8574
发现这个社区不错,所以也凑个热闹。
第一篇日志,一定要动手写才有诚意。
这两天要给刚做的外网系统登录页面加验证码,以前没做过。上网搜了一下,资料很多。验证码校验称作captcha:
Completely Automated Public Test to tell Computers and Humans Apart
专业点儿的翻译是:全自动区分计算机和人类的图灵测试。
CAPTCHA的目的很明确,就是区分计算机和人类的一种程序算法,
这种程序必须能生成并评价人类能很容易通过但计算机却通不过的测试。
网上能查到不少实现方案,简单的写个jsp就行了,技术含量不高。搜了一圈后,
感觉还是用个正规点儿比较合适,然后就锁定了JCaptcha,到其官方网站上看了看:
http://jcaptcha.octo.com/confluence/display/general/Home
JCaptcha提供了
Provide robust and reliable CAPTCHA implementation framework for JAVA
Provide accessible CAPTCHA implementations
Provide multi-type challenge (text, sound, image)
它用上百个类来实现了如此简单的功能,这是为什么呢?官方给的解释是
1、学术界能不断的发明(或发现)一些人类容易处理而机器不能很好处理的问题。
JCaptcha高屋建瓴的给出了一种通用的定义和表达这种问题并用于识别的方案。
也就是识别方案的可扩展性。
2、实现了若干引擎和组件,通过配置这些引擎和组件,可以方便的修改自己程序
captcha构件的算法。这样,在抵御恶意访问时,可以不用改变代码,灵活快速的
改变captcha策略,从而更好的保护系统。
个人觉的说的挺好,第一点对于我们来说倒是次要的,主要第二点比较有意义。
然后就试了试。下面是具体需要做的工作:
一,从官网上下个jcaptcha-1.0-all.jar,加入到项目中,
官方的2.0还没有正式版,所以先用1.0吧。
二,官网上介绍了几种和项目结合的具体方案,最简单的方式很快走通,图片很难看,
而且不具有可配置性,肯定不行。所以选择通过spring来整合的方式,
spring是整合和配置的平台,把jcaptcha的服务和引擎还有组件配置成spring的bean。
示例如下:
<?xml version="1.0" encoding="gb2312"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans default-autowire="byName"> <bean id="captchaServlet" class="com.jawava.XXXX.XXX.TopImageCaptchaServlet" /> <bean id="captchaService" class="com.octo.captcha.service.multitype.GenericManageableCaptchaService"> <description>验证码服务</description> <constructor-arg index="0"><ref bean="imageEngine"/></constructor-arg> <constructor-arg index="1"><value>300</value></constructor-arg><!--超时时间 秒--> <constructor-arg index="2"><value>20000</value></constructor-arg><!--最大并发数--> <constructor-arg index="3"><value>20000</value></constructor-arg>& lt;!--第四个参数官网示例上没有给出,会报错,后来看了API才知道少了个参数--> </bean> <bean id="imageEngine" class="com.octo.captcha.engine.GenericCaptchaEngine"> <description>图片引擎</description> <constructor-arg index="0"> <list> <ref bean="CaptchaFactory"/> </list> </constructor-arg> </bean> <bean id="CaptchaFactory" class="com.octo.captcha.image.gimpy.GimpyFactory" > <description>验证码工厂</description> <constructor-arg><ref bean="wordgen"/></constructor-arg> <constructor-arg><ref bean="wordtoimage"/></constructor-arg> </bean> <bean id="wordgen" class= "com.octo.captcha.component.word.wordgenerator.RandomWordGenerator" > <description>文字产生器,提供了好几种实现,经过比较选用了这种</description> <constructor-arg index="0"><value>0123456789</value></constructor-arg> </bean> <bean id="wordtoimage" class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage" > <description>图片生成器</description> <constructor-arg index="0"><ref bean="fontGenRandom"/></constructor-arg> <constructor-arg index="1"><ref bean="backGenUni"/></constructor-arg> <constructor-arg index="2"><ref bean="simpleWhitePaster"/></constructor-arg> </bean> <bean id="fontGenRandom" class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator" > <description>文字转换图片</description> <constructor-arg index="0"><value>20</value></constructor-arg><!--字体最小尺寸--> <constructor-arg index="1"><value>20</value></constructor-arg><!--字体最大尺寸--> </bean> <bean id="backGenUni" class="com.octo.captcha.component.image.backgroundgenerator.GradientBackgroundGenerator" > <constructor-arg index="0"><value>62</value></constructor-arg><!--背景图片宽度--> <constructor-arg index="1"><value>22</value></constructor-arg><!--背景图片高度--> <constructor-arg type="java.awt.Color" index="2"> <ref bean="colorGrey"/> </constructor-arg> <constructor-arg type="java.awt.Color" index="3"> <ref bean="colorGreen"/> </constructor-arg> </bean> <bean id="simpleWhitePaster" class="com.octo.captcha.component.image.textpaster.SimpleTextPaster" > <constructor-arg type="java.lang.Integer" index="0"> <value>4</value><!--字符最少个数--> </constructor-arg> <constructor-arg type="java.lang.Integer" index="1"> <value>4</value><!--字符最多个数--> </constructor-arg> <constructor-arg type="java.awt.Color" index="2"> <ref bean="colorFont"/> </constructor-arg> </bean> <bean id="colorGrey" class="java.awt.Color" > <constructor-arg index="0"><value>200</value></constructor-arg> <constructor-arg index="1"><value>255</value></constructor-arg> <constructor-arg index="2"><value>200</value></constructor-arg> </bean> <bean id="colorGreen" class="java.awt.Color" > <constructor-arg index="0"><value>110</value></constructor-arg> <constructor-arg index="1"><value>120</value></constructor-arg> <constructor-arg index="2"><value>200</value></constructor-arg> </bean> <bean id="colorFont" class="java.awt.Color" > <constructor-arg index="0"><value>60</value></constructor-arg> <constructor-arg index="1"><value>60</value></constructor-arg> <constructor-arg index="2"><value>60</value></constructor-arg> </bean> </beans> |
这里面具体用哪个component,需要看ApI,我把包里提供的现成的组件基本上试了大半,
最后选择了如上的配置,选择的标准一是美观,二是识别率。识别率太低了,用户体验会下降。
三、配置好后,JCaptcha这边的工作就完成了。下面就是和项目的结合。
1、首先专门写个CaptcahServlet用来获取验证码,由于要在servlet里注入spring的bean,
所以用了代理的方式。代理类网上有,都是固定写法,这里就不贴了。
CaptcahServlet主要部分如下:
@Override public void service(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException, RuntimeException { byte[] captchaChallengeAsJpeg = null; //输出jpg的字节流 ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream(); try { // get the session id that will identify the generated captcha. //the same id must be used to validate the response, the session id is a good candidate! String captchaId = httpServletRequest.getSession().getId(); // call the ImageCaptchaService getChallenge method BufferedImage challenge = (BufferedImage) captchaService.getChallengeForID(captchaId, httpServletRequest.getLocale()); // a jpeg encoder JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream); jpegEncoder.encode(challenge); } catch (IllegalArgumentException e) { httpServletResponse.sendError(HttpServletResponse.SC_NOT_FOUND); return; } catch (CaptchaServiceException e) { httpServletResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); return; } captchaChallengeAsJpeg = jpegOutputStream.toByteArray(); // flush it in the response httpServletResponse.setHeader("Cache-Control", "no-store"); httpServletResponse.setHeader("Pragma", "no-cache"); httpServletResponse.setDateHeader("Expires", 0); httpServletResponse.setContentType("image/jpeg"); ServletOutputStream responseOutputStream = httpServletResponse.getOutputStream(); responseOutputStream.write(captchaChallengeAsJpeg); responseOutputStream.flush(); responseOutputStream.close(); } |
然后在web.xml配置:
<servlet> <servlet-name>CaptchaProxy</servlet-name> <servlet-class>com.jawava.XXXXX.XXX.TopHttpServletProxy</servlet-class> <init-param> <param-name>targetServlet</param-name> <param-value>captchaServlet</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>CaptchaProxy</servlet-name> <url-pattern>/topJcaptcha</url-pattern> </servlet-mapping> |
2、目前项目的登录页面是jsp,结合很方便,在原有页面上加上
<img src="/topJcaptcha" /> <input type="text" name="jcaptcha" value="" />
那个img就是校验码。
需要的话,再加上个换图片的功能,很简单。
3、验证环节。我是在原来的验证用户密码的逻辑前,加上了验证码的校验逻辑。
很简单的几句话,代码如下:
Boolean isResponseCorrect =Boolean.FALSE; //需要sessionId 来验证校验码 String sessionId = request.getSession().getId(); //首先校验验证码 try { isResponseCorrect = captchaService.validateResponseForID(sessionId, captcha_input); if(!isResponseCorrect) { throw new RuntimeException("输入的验证码有误,请重新输入"); } } catch (CaptchaServiceException e) { //should not happen, may be thrown if the id is not valid throw new RuntimeException("校验验证码时出现不明错误",e); } |
四、如此就可以测试了。通过反复调整参数,达到了比较美观的效果,但是文字始终
不在图片的正中,而是靠上,甚至都把文字掩盖了一半。翻看了半天API,也看不出
个端倪。只好去翻源代码了。问题出在
com.octo.captcha.component.image.textpaster.SimpleTextPaster这个类,它把文字
图片往背景图片放时,把文字的位置放在了背景图片一半的高度上,这就是问题所在。
修改代码重新打了个jar包。替换一下,重启后一切ok。