方法三:
用开源组件Jcaptcha实现,与Spring组合使用可产生多种形式的验证码,JCaptcha 即为 Java 版本的 CAPTCHA 项目,其是一个开源项目,支持生成图形和声音版的验证码,在生成声音版的验证码时,需要使用到 FreeTTS。而CAPTCHA 全称 Completely Automated Public Turing Test to Tell Computers and Humans Apart,最早作为卡内基梅隆大学的一个科研项目,用于生成一个人类容易通过而计算机难以通过的测试,目前广泛应用于网络应用,用于阻止机器人发布垃圾信息。目前,JCaptcha 官方网站显示有 2.0 版本,但二进制版只有 1.0 版可供下载。
首先我们需准备相应的jar包
JCaptcha 项目在实现中,还引用了 commons-collections 和 commons-logging 两个开源项目,再加上 JCaptcha 本身的实现,我们共需要三个包,具体信息如下:
- jcaptcha-2.0-all.jar
- commons-logging-1.1.1.jar
- commons-collections-3.2.jar
其次我们看下面web.xml中的关键配置信息:除配置了Jcaptcha组件的具体class路径外,还配置了SubmitActionServlet这个用于比对验证码的servlet,并且都为二者做了映射,可理解为换了名字,分别为/jcaptcha.jpg和/submit.action调用组件或servlet时直接用这个映射名即可。
1 <servlet> 2 <servlet-name>jcaptcha</servlet-name> 3 <servlet-class>com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet</servlet-class> 4 </servlet> 5 6 <servlet> 7 <servlet-name>submit</servlet-name> 8 <servlet-class>com.octo.captcha.module.servlet.image.sample.SubmitActionServlet</servlet-class> 9 </servlet> 10 11 <servlet-mapping> 12 <servlet-name>jcaptcha</servlet-name> 13 <url-pattern>/jcaptcha.jpg</url-pattern> 14 </servlet-mapping> 15 16 <servlet-mapping> 17 <servlet-name>submit</servlet-name> 18 <url-pattern>/submit.action</url-pattern> 19 </servlet-mapping>
web.xml
然后来看SubmitActionServlet怎么实现:由于导入了jcaptcha的组件包,所以直接调用其中封装好的方法SimpleImageCaptchaServlet.validateResponse(request, userCaptchaResponse)来判断验证码中的信息是否与提交的匹配,而不需要去考虑具体的实现过程。
1 public class SubmitActionServlet extends HttpServlet 2 { 3 protected void doPost(HttpServletRequest request, HttpServletResponse response) 4 throws ServletException, IOException 5 { 6 String userCaptchaResponse = request.getParameter("japtcha"); 7 boolean captchaPassed = SimpleImageCaptchaServlet.validateResponse(request, userCaptchaResponse); 8 if (captchaPassed) 9 response.getWriter().write("captcha passed"); 10 else { 11 response.getWriter().write("captcha failed"); 12 } 13 response.getWriter().write("<br/><a href=‘index.jsp‘>Try again</a>"); 14 } 15 }
SubmitActionServlet
最后我们看一下简单的前台调用:
1 <html> 2 <body> 3 <h2>Simple Captcha Servlet sample</h2> 4 <form action="submit.action" method="post"> 5 <img src="jcaptcha.jpg" /> <input type="text" name="japtcha" value="" /> 6 <input type="submit"/> 7 </form> 8 </body> 9 </html>
index.jsp
实现图例:
方法四:
用开源组件kaptcha实现,同样使用kaptcha需要下载其jar组件包,kaptcha 是一个非常实用的验证码生成工具。有了它,你可以生成各种样式的验证码,因为它是可配置的。kaptcha工作的原理是调用 com.google.code.kaptcha.servlet.KaptchaServlet(可以看出他是谷歌的一个开源项目),生成一个图片。同时将生成的验证码字符串放到 HttpSession中。
使用kaptcha可以方便的配置:
- 验证码的字体
- 验证码字体的大小
- 验证码字体的字体颜色
- 验证码内容的范围(数字,字母,中文汉字!)
- 验证码图片的大小,边框,边框粗细,边框颜色
- 验证码的干扰线(可以自己继承com.google.code.kaptcha.NoiseProducer写一个自定义的干扰线)
- 验证码的样式(鱼眼样式、3D、普通模糊……当然也可以继承com.google.code.kaptcha.GimpyEngine自定义样式)
……
对kaptcha的配置信息同样的是放在了web.xml中:<init-param>标签中即初始化的配置信息
1 <servlet> 2 <servlet-name>Kaptcha</servlet-name> 3 <servlet-class> 4 com.google.code.kaptcha.servlet.KaptchaServlet 5 </servlet-class> 6 <init-param> 7 <description>图片边框,合法值:yes , no</description> 8 <param-name>kaptcha.border</param-name> 9 <param-value>yes</param-value> 10 </init-param> 11 <init-param> 12 <description> 13 边框颜色,合法值: r,g,b (and optional alpha) 或者 14 white,black,blue. 15 </description> 16 <param-name>kaptcha.border.color</param-name> 17 <param-value>black</param-value> 18 </init-param> 19 <init-param> 20 <description>边框厚度,合法值:>0</description> 21 <param-name>kaptcha.border.thickness</param-name> 22 <param-value>1</param-value> 23 </init-param> 24 <init-param> 25 <description>图片宽 200</description> 26 <param-name>kaptcha.image.width</param-name> 27 <param-value>200</param-value> 28 </init-param> 29 <init-param> 30 <description>图片高 50</description> 31 <param-name>kaptcha.image.height</param-name> 32 <param-value>50</param-value> 33 </init-param> 34 <init-param> 35 <description>图片实现类</description> 36 <param-name>kaptcha.producer.impl</param-name> 37 <param-value> 38 com.google.code.kaptcha.impl.DefaultKaptcha 39 </param-value> 40 </init-param> 41 <init-param> 42 <description>文本实现类</description> 43 <param-name>kaptcha.textproducer.impl</param-name> 44 <param-value> 45 com.google.code.kaptcha.text.impl.DefaultTextCreator 46 </param-value> 47 </init-param> 48 <init-param> 49 <description>文本集合,验证码值从此集合中获取</description> 50 <param-name>kaptcha.textproducer.char.string</param-name> 51 <param-value>1234567890</param-value> 52 <!--<param-value>abcde2345678gfynmnpwx</param-value>--> 53 <!--<param-value>慕课网教程验证码实例</param-value> --> 54 </init-param> 55 <init-param> 56 <description>验证码长度 5</description> 57 <param-name>kaptcha.textproducer.char.length</param-name> 58 <param-value>4</param-value> 59 </init-param> 60 <init-param> 61 <description>字体 Arial, Courier</description> 62 <param-name>kaptcha.textproducer.font.names</param-name> 63 <param-value>Arial, Courier</param-value> 64 </init-param> 65 <init-param> 66 <description>字体大小 40px.</description> 67 <param-name>kaptcha.textproducer.font.size</param-name> 68 <param-value>40</param-value> 69 </init-param> 70 <init-param> 71 <description> 72 字体颜色,合法值: r,g,b 或者 white,black,blue. 73 </description> 74 <param-name>kaptcha.textproducer.font.color</param-name> 75 <param-value>black</param-value> 76 </init-param> 77 <init-param> 78 <description>文字间隔 2</description> 79 <param-name>kaptcha.textproducer.char.space</param-name> 80 <param-value>2</param-value> 81 </init-param> 82 <init-param> 83 <description>干扰实现类</description> 84 <param-name>kaptcha.noise.impl</param-name> 85 <param-value> 86 <!-- com.google.code.kaptcha.impl.NoNoise --> 87 com.google.code.kaptcha.impl.DefaultNoise 88 </param-value> 89 </init-param> 90 <init-param> 91 <description> 92 干扰颜色,合法值: r,g,b 或者 white,black,blue. 93 </description> 94 <param-name>kaptcha.noise.color</param-name> 95 <param-value>black</param-value> 96 </init-param> 97 <init-param> 98 <description> 99 图片样式: 水纹com.google.code.kaptcha.impl.WaterRipple 100 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 101 阴影com.google.code.kaptcha.impl.ShadowGimpy 102 </description> 103 <param-name>kaptcha.obscurificator.impl</param-name> 104 <param-value> 105 com.google.code.kaptcha.impl.WaterRipple 106 </param-value> 107 </init-param> 108 <init-param> 109 <description>背景实现类</description> 110 <param-name>kaptcha.background.impl</param-name> 111 <param-value> 112 com.google.code.kaptcha.impl.DefaultBackground 113 </param-value> 114 </init-param> 115 <init-param> 116 <description>背景颜色渐变,开始颜色</description> 117 <param-name>kaptcha.background.clear.from</param-name> 118 <param-value>green</param-value> 119 </init-param> 120 <init-param> 121 <description>背景颜色渐变,结束颜色</description> 122 <param-name>kaptcha.background.clear.to</param-name> 123 <param-value>white</param-value> 124 </init-param> 125 <init-param> 126 <description>文字渲染器</description> 127 <param-name>kaptcha.word.impl</param-name> 128 <param-value> 129 com.google.code.kaptcha.text.impl.DefaultWordRenderer 130 </param-value> 131 </init-param> 132 <init-param> 133 <description> 134 session中存放验证码的key键 135 </description> 136 <param-name>kaptcha.session.key</param-name> 137 <param-value>KAPTCHA_SESSION_KEY</param-value> 138 </init-param> 139 <init-param> 140 <description> 141 The date the kaptcha is generated is put into the 142 HttpSession. This is the key value for that item in the 143 session. 144 </description> 145 <param-name>kaptcha.session.date</param-name> 146 <param-value>KAPTCHA_SESSION_DATE</param-value> 147 </init-param> 148 </servlet> 149 <servlet-mapping> 150 <servlet-name>Kaptcha</servlet-name> 151 <url-pattern>/randomcode.jpg</url-pattern> 152 </servlet-mapping>
web.xml
前台调用:验证码图片的路径用了映射名randomcode.jpg,点击事件onclick()调用了js函数,js函数中同样的用当前时间使浏览器缓存失效来刷新验证码图片。
1 <html> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 4 <title>randomcode</title> 5 <script type="text/javascript"> 6 function changeR(node){ 7 // 用于点击时产生不同的验证码 8 node.src = "randomcode.jpg?time="+new Date().getTime() ; 9 } 10 </script> 11 </head> 12 <body> 13 <img alt="random" src="randomcode.jpg" onclick="changeR(this)" style="cursor: pointer;"> 14 <form action="check.jsp"> 15 <input type="text" name="r"> 16 <input type="submit" value="s"> 17 </form> 18 </body> 19 </html>
index.jsp
验证码的比对check.jsp:通过(String) session.getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY);得到验证码图片中字符串信息。
1 <html> 2 <head> 3 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 4 <title>check</title> 5 </head> 6 <body> 7 <% 8 // 检查是否是正确的验证码 9 String k = (String) session 10 .getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); 11 String str = request.getParameter("r"); 12 if (k.equals(str)) 13 out.print("true"); 14 out.print(k + "---" + str); 15 %> 16 </body> 17 </html>
check.jsp
实现图例:
可以发现我们的验证码变得越来越来丰富多彩了,但是还停留在一个只验证英文字母和数字的阶段,那么还能不能玩点高端的呢,答案是肯定的。接下来我们来看一下中文验证码和算术运算验证码怎么实现。都只需基于以上kaptcha的使用进行修改即可。
中文验证码的实现:
首先我们找到kaptcha.jar包下有个DefaultTextCreator.class字节码文件,顾名思义,他是来产生验证码中文本的一个类,我们可以通过自己实现一个继承于他的类,并通过配置来使用自己的实现类来使验证码中的文本变为中文。经过如下反编译后得到的类我们可以看到其是那样实现的,其中的getText()函数用于产生getConfig()配置器将要渲染的验证码文本,所以我们只需继承配置类并实现文本产生接口后重写getText()方法即可。
1 public class DefaultTextCreator 2 extends Configurable 3 implements TextProducer 4 { 5 public String getText() 6 { 7 int length = getConfig().getTextProducerCharLength(); 8 char[] chars = getConfig().getTextProducerCharString(); 9 int randomContext = chars.length - 1; 10 Random rand = new Random(); 11 StringBuffer text = new StringBuffer(); 12 for (int i = 0; i < length; i++) { 13 text.append(chars[(rand.nextInt(randomContext) + 1)]); 14 } 15 return text.toString(); 16 } 17 }
DefaultTextCreator.class反编译结果
如下为ChineseText.java的具体实现:其中程序只执行getText()中的代码,原来代码写到了getText1()中,不执行,大家可以做一个比较。
1 public class ChineseText extends Configurable implements TextProducer { 2 3 public String getText() { 4 int length = getConfig().getTextProducerCharLength(); 5 //char[] charS = getConfig().getTextProducerCharString(); 6 7 String[] s = new String[]{"我","爱","扎","瓦","和","卖","塞","扣"}; 8 9 Random rand = new Random(); 10 StringBuffer sb = new StringBuffer(); 11 for(int i = 0; i < length; i++){ 12 int ind = rand.nextInt(s.length); 13 sb.append(s[ind]); 14 } 15 return sb.toString(); 16 } 17 /** 18 * 中午实例 19 * @return 20 */ 21 public String getText1() { 22 int length = getConfig().getTextProducerCharLength(); 23 String finalWord = "", firstWord = ""; 24 int tempInt = 0; 25 String[] array = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 26 "a", "b", "c", "d", "e", "f" }; 27 28 Random rand = new Random(); 29 30 for (int i = 0; i < length; i++) { 31 switch (rand.nextInt(array.length)) { 32 case 1: 33 tempInt = rand.nextInt(26) + 65; 34 firstWord = String.valueOf((char) tempInt); 35 break; 36 case 2: 37 int r1, 38 r2, 39 r3, 40 r4; 41 String strH, 42 strL;// high&low 43 r1 = rand.nextInt(3) + 11; // 前闭后开[11,14) 44 if (r1 == 13) { 45 r2 = rand.nextInt(7); 46 } else { 47 r2 = rand.nextInt(16); 48 } 49 50 r3 = rand.nextInt(6) + 10; 51 if (r3 == 10) { 52 r4 = rand.nextInt(15) + 1; 53 } else if (r3 == 15) { 54 r4 = rand.nextInt(15); 55 } else { 56 r4 = rand.nextInt(16); 57 } 58 59 strH = array[r1] + array[r2]; 60 strL = array[r3] + array[r4]; 61 62 byte[] bytes = new byte[2]; 63 bytes[0] = (byte) (Integer.parseInt(strH, 16)); 64 bytes[1] = (byte) (Integer.parseInt(strL, 16)); 65 66 firstWord = new String(bytes); 67 break; 68 default: 69 tempInt = rand.nextInt(10) + 48; 70 firstWord = String.valueOf((char) tempInt); 71 break; 72 } 73 finalWord += firstWord; 74 } 75 return finalWord; 76 } 77 }
ChineseText.java
最后一步就是去web.xml中改文本实现类的值,使组件调用自己写的那个类产生中文验证码。
1 <init-param> 2 <description>文本实现类</description> 3 <param-name>kaptcha.textproducer.impl</param-name> 4 <param-value> 5 ChineseText 6 </param-value> 7 </init-param>
web.xml修改处
实现图例:
算数运算验证码的实现:
和上述中文验证码相同,我们需要通过继承类和接口来实现自己的类,并改写其中的函数,然后通过改配置信息来使组件调用自己实现的类从而实现验证码形式的多样化。
KaptchaServlet字节码文件经过反编译后的代码如下:
1 public class KaptchaServlet 2 extends HttpServlet 3 implements Servlet 4 { 5 private Properties props = new Properties(); 6 private Producer kaptchaProducer = null; 7 private String sessionKeyValue = null; 8 9 public void init(ServletConfig conf) 10 throws ServletException 11 { 12 super.init(conf); 13 14 ImageIO.setUseCache(false); 15 16 Enumeration<?> initParams = conf.getInitParameterNames(); 17 while (initParams.hasMoreElements()) 18 { 19 String key = (String)initParams.nextElement(); 20 String value = conf.getInitParameter(key); 21 this.props.put(key, value); 22 } 23 Config config = new Config(this.props); 24 this.kaptchaProducer = config.getProducerImpl(); 25 this.sessionKeyValue = config.getSessionKey(); 26 } 27 28 public void doGet(HttpServletRequest req, HttpServletResponse resp) 29 throws ServletException, IOException 30 { 31 resp.setDateHeader("Expires", 0L); 32 33 resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 34 35 resp.addHeader("Cache-Control", "post-check=0, pre-check=0"); 36 37 resp.setHeader("Pragma", "no-cache"); 38 39 resp.setContentType("image/jpeg"); 40 41 String capText = this.kaptchaProducer.createText(); 42 43 req.getSession().setAttribute(this.sessionKeyValue, capText); 44 45 BufferedImage bi = this.kaptchaProducer.createImage(capText); 46 47 ServletOutputStream out = resp.getOutputStream(); 48 49 ImageIO.write(bi, "jpg", out); 50 try 51 { 52 out.flush(); 53 } 54 finally 55 { 56 out.close(); 57 } 58 } 59 }
KaptchaServlet.class反编译结果
然后自己实现的KaptchaServlet.java代码如下:在原来的字节码文件上增加了实现加法验证码的逻辑,通过比对即可发现。
1 public class KaptchaServlet extends HttpServlet implements Servlet { 2 private Properties props; 3 private Producer kaptchaProducer; 4 private String sessionKeyValue; 5 6 public KaptchaServlet() { 7 this.props = new Properties(); 8 9 this.kaptchaProducer = null; 10 11 this.sessionKeyValue = null; 12 } 13 14 public void init(ServletConfig conf) throws ServletException { 15 super.init(conf); 16 17 ImageIO.setUseCache(false); 18 19 Enumeration initParams = conf.getInitParameterNames(); 20 while (initParams.hasMoreElements()) { 21 String key = (String) initParams.nextElement(); 22 String value = conf.getInitParameter(key); 23 this.props.put(key, value); 24 } 25 26 Config config = new Config(this.props); 27 this.kaptchaProducer = config.getProducerImpl(); 28 this.sessionKeyValue = config.getSessionKey(); 29 } 30 31 public void doGet(HttpServletRequest req, HttpServletResponse resp) 32 throws ServletException, IOException { 33 resp.setDateHeader("Expires", 0L); 34 35 resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 36 37 resp.addHeader("Cache-Control", "post-check=0, pre-check=0"); 38 39 resp.setHeader("Pragma", "no-cache"); 40 41 resp.setContentType("image/jpeg"); 42 43 String capText = this.kaptchaProducer.createText(); 44 String s1 = capText.substring(0, 1); 45 String s2 = capText.substring(1, 2); 46 int r = Integer.valueOf(s1).intValue() + Integer.valueOf(s2).intValue(); 47 48 req.getSession().setAttribute(this.sessionKeyValue, String.valueOf(r)); 49 50 BufferedImage bi = this.kaptchaProducer.createImage(s1+"+"+s2+"=?"); 51 52 ServletOutputStream out = resp.getOutputStream(); 53 54 ImageIO.write(bi, "jpg", out); 55 try { 56 out.flush(); 57 } finally { 58 out.close(); 59 } 60 } 61 }
KaptchaServlet.java
我们还需在web.xml开头进行更改配置使组件来调用自己实现的servlet:
1 <servlet-name>Kaptcha</servlet-name> 2 <servlet-class> 3 KaptchaServlet 4 </servlet-class>
web.xml部分更改
实现图例:
总结:
这里只是简单实现了验证码,但要用到商业项目上还是远远不够的,验证码想要不被破解还要加上各种加密算法,即使这样,也可以随处搜索到某某验证码被破解的情况。在网络上有一个地下数据库,里面存储的是我们的个人信息,那些信息都是一条条整理好的,这十分可怕,但这样一个数据库是客观存在的,验证码作为保护账户密码的重要一环,已变得越来越不安全,因为个人信息的泄露,别人掌握了你足够的信息就可以对你实行诈骗,其中验证码是重要一环,近日就有一毕业生被骗取验证码后,几个小时内所有财富都被转走的事件发生。所以请保护好你的验证码,不要轻易泄露给他人。
当然我们也不会坐以待毙,目前的验证码种类繁多也越来越难以破解,像语音识别、面部识别这些都是耳熟能详的就不多说了。
这里我们引入一个验证码“新”概念:双因素认证。
双因素认证是一种采用时间同步技术的系统,采用了基于时间、事件和密钥三变量而产生的一次性密码来代替传统的静态密码。每个动态密码卡都有一个唯一的密钥,该密钥同时存放在服务器端,每次认证时动态密码卡与服务器分别根据同样的密钥,同样的随机参数(时间、事件)和同样的算法计算了认证的动态密码,从而确保密码的一致性,从而实现了用户的认证。因每次认证时的随机参数不同,所以每次产生的动态密码也不同。由于每次计算时参数的随机性保证了每次密码的不可预测性,从而在最基本的密码认证这一环节保证了系统的安全性。解决因口令欺诈而导致的重大损失,防止恶意入侵者或人为破坏,解决由口令泄密导致的入侵问题。
简单来说,双因素身份认证就是通过你所知道再加上你所能拥有的这二个要素组合到一起才能发挥作用的身份认证系统。例如,在ATM上取款的银行卡就是一个双因素认证机制的例子,需要知道取款密码和银行卡这二个要素结合才能使用。
目前主流的双因素认证系统是基于时间同步型,市场占有率高的有DKEY双因素认证系统、RSA双因素认证系统等,由于DKEY增加对短信密码认证及短信+令牌混合认证支持,相比RSA,DKEY双因素认证系统更具竞争力。