驗證碼概述
為什么使用驗證碼?
驗證碼(CAPTCHA)是一種全自動程序。主要是為了區分“進行操作的是不是人”。如果沒有驗證碼機制,將會導致以下的問題:
- 對特定網站不斷進行登錄,破解密碼;
- 對某個網站創建賬戶;
- 對某個網站提交垃圾數據(灌水貼);
- 對某個網站進行刷票。
使用Servlet實現驗證碼
一個驗證碼包含兩個部分:圖片和輸入框。
1 <script type="text/javascript"> 2 function reloadCode(){ 3 var time = new Date(); 4 // 給URL傳遞參數可以清空瀏覽器的緩存,讓瀏覽器認為這是一個新的請求 5 document.getElementById('safecode').src = '<%=request.getContextPath()%>/servlet/ImageServlet?d=' + time; 6 } 7 </script> 8 9 <form action="<%=request.getContextPath()%>/servlet/ValidateImageServlet"method="post"> 10 驗證碼:<img src="<%=request.getContextPath()%>/servlet/ImageServlet" alt="驗證碼" id="safecode"> 11 <input type="text" id="verifyCode" name="verifyCode" size="6" /> 12 <a href="javascript:reloadCode();">看不清楚</a><br> 13 <input type="submit" value="登錄" /> 14 </form>
我們用ImageServlet實時生成圖片。生成圖片所需要的步驟如下:
1 定義BufferedImage對象 2 獲得Graphics對象 3 聽過Random類產生隨機驗證碼信息 4 使用Graphics繪制圖片 5 記錄驗證碼信息到session中 6 使用ImageIO輸出圖片
檢驗驗證碼是否正確:ValidateImageServlet:
1 獲取頁面的驗證碼 2 獲取session中保存的驗證碼 3 比較驗證碼 4 返回校驗結果
驗證的流程如下:
生成驗證碼的ImageServlet:
1 private static Random r = new Random(); 2 private static char[] chs = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray(); 3 private static final int NUMBER_OF_CHS = 4; 4 private static final int IMG_WIDTH = 65; 5 private static final int IMG_HEIGHT = 25; 6 7 8 public void doGet(HttpServletRequest request, HttpServletResponse response) 9 throws ServletException, IOException { 10 11 BufferedImage image = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, BufferedImage.TYPE_INT_RGB); // 實例化BufferedImage 12 Graphics g = image.getGraphics(); 13 Color c = new Color(200, 200, 255); // 驗證碼圖片的背景顏色 14 g.setColor(c); 15 g.fillRect(0, 0, IMG_WIDTH, IMG_HEIGHT); // 圖片的邊框 16 17 StringBuffer sb = new StringBuffer(); // 用於保存驗證碼字符串 18 int index; // 數組的下標 19 for (int i = 0; i < NUMBER_OF_CHS; i++) { 20 index = r.nextInt(chs.length); // 隨機一個下標 21 g.setColor(new Color(r.nextInt(88), r.nextInt(210), r.nextInt(150))); // 隨機一個顏色 22 g.drawString(chs[index] + "", 15 * i + 3, 18); // 畫出字符 23 sb.append(chs[index]); // 驗證碼字符串 24 } 25 26 request.getSession().setAttribute("piccode", sb.toString()); // 將驗證碼字符串保存到session中 27 ImageIO.write(image, "jpg", response.getOutputStream()); // 向頁面輸出圖像 28 } 29 30 public void doPost(HttpServletRequest request, HttpServletResponse response) 31 throws ServletException, IOException { 32 doGet(request, response); 33 } 34 35 }
進行驗證碼圖片驗證的Servlet:
1 public class ValidateImageServlet extends HttpServlet { 2 3 public void doGet(HttpServletRequest request, HttpServletResponse response) 4 throws ServletException, IOException { 5 6 doPost(request, response); 7 } 8 9 public void doPost(HttpServletRequest request, HttpServletResponse response) 10 throws ServletException, IOException { 11 12 response.setContentType("text/html;charset=utf-8"); 13 String picString = (String) request.getSession().getAttribute("piccode"); 14 String checkCode = request.getParameter("verifyCode"); 15 PrintWriter out = response.getWriter(); 16 if (picString.toUpperCase().equals(checkCode.toUpperCase())) 17 out.println("驗證碼正確"); 18 else 19 out.print("驗證碼錯誤!"); 20 21 out.flush(); 22 out.close(); 23 } 24 25 }
開源組件實現驗證碼
Jcaptcha:
一個用來生成圖形驗證碼的開源組件,可以產生多種形式的驗證碼。可以與Spring組合使用。需要導入的jar包如下:
用於展示驗證碼的auth_code_captcha.jsp如下:
1 <form action="submit.action" method="post"> 2 <img src="jcaptcha.jpg" /> <input type="text" name="japtcha" value="" /> 3 <input type="submit"/> 4 </form>
web.xml的配置如下:
1 <servlet> 2 <servlet-name>jcaptcha</servlet-name> 3 <servlet-class>com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet</servlet-class> 4 </servlet> 5 <!-- 處理表單提交的Servlet --> 6 <servlet> 7 <servlet-name>submit</servlet-name> 8 <servlet-class>org.gpf.servlet.SubmitActionServlet</servlet-class> 9 </servlet> 10 <servlet-mapping> 11 <servlet-name>jcaptcha</servlet-name> 12 <url-pattern>/jcaptcha.jpg</url-pattern> 13 </servlet-mapping> 14 <servlet-mapping> 15 <servlet-name>submit</servlet-name> 16 <url-pattern>/submit.action</url-pattern> 17 </servlet-mapping>
表單提交的Servlet:
1 package org.gpf.servlet; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServlet; 7 import javax.servlet.http.HttpServletRequest; 8 import javax.servlet.http.HttpServletResponse; 9 10 import com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet; 11 /** 12 * 圖片驗證碼的captcha實現 13 * @author gaopengfei 14 * @date 2015-5-20 下午9:58:20 15 */ 16 public class SubmitActionServlet extends HttpServlet { 17 18 private static final long serialVersionUID = 1L; 19 20 protected void doPost(HttpServletRequest request, 21 HttpServletResponse response) throws ServletException, IOException { 22 23 String userCaptchaResponse = request.getParameter("japtcha"); 24 boolean captchaPassed = SimpleImageCaptchaServlet.validateResponse(request, userCaptchaResponse); 25 26 response.setContentType("text/html;charset=utf-8"); 27 if (captchaPassed) 28 response.getWriter().write("驗證通過!"); 29 else { 30 response.getWriter().write("驗證失敗!"); 31 } 32 response.getWriter().write("<br/><a href='auth_code_captcha.jsp'>重新驗證</a>"); 33 } 34 }
kaptcha:
它是可以配置的,也可以生成各種樣式的驗證碼。如下是其簡單應用:用於顯示驗證碼的index.jsp
1 <img alt="驗證碼圖片" src="random.jpg"> 2 <form action="check.jsp" method="post"> 3 <input type="text" name="imageText"> 4 <input type="submit" value="驗證"> 5 </form>
用於驗證驗證碼的check.jsp
1 <%@ page import="com.google.code.kaptcha.Constants" %> 2 <%@ page language="java" import="java.util.*" contentType="text/html; charset=utf-8"%> 3 <% 4 String myImageText = request.getParameter("imageText"); 5 String key = (String)request.getSession().getAttribute(com.google.code.kaptcha.Constants.KAPTCHA_SESSION_KEY); 6 7 if(!myImageText.isEmpty() && myImageText.equals(key)) 8 out.print("驗證通過!<br />"); 9 else 10 out.print("驗證失敗!<br />"); 11 out.print("你輸入的字符:" + myImageText + ",驗證碼字符:" + key); 12 %>
配置圖片顯示的Servlet:
1 <servlet> 2 <servlet-name>Kcaptcha</servlet-name> 3 <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> 4 </servlet> 5 <servlet-mapping> 6 <servlet-name>Kcaptcha</servlet-name> 7 <url-pattern>/random.jpg</url-pattern> 8 </servlet-mapping>
Kaptcha的詳細配置
1 <servlet> 2 <servlet-name>Kcaptcha</servlet-name> 3 <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class> 4 5 <!-- 邊框 --> 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>邊框顏色,white、black(默認)、blue等</description> 13 <param-name>kaptcha.border.color</param-name> 14 <param-value>green</param-value> 15 </init-param> 16 <init-param> 17 <description>邊框厚度大於0</description> 18 <param-name>kaptcha.border.thickness</param-name> 19 <param-value>10</param-value> 20 </init-param> 21 22 <!-- 圖片寬高 --> 23 <init-param> 24 <description>圖片寬度</description> 25 <param-name>kaptcha.image.width</param-name> 26 <param-value>200</param-value> 27 </init-param> 28 <init-param> 29 <description>圖片高度</description> 30 <param-name>kaptcha.image.height</param-name> 31 <param-value>60</param-value> 32 </init-param> 33 34 <!-- 圖片樣式 --> 35 <init-param> 36 <description>圖片樣式:水紋(WaterRipple)、魚眼(FishEyeGimpy)、陰影(ShadowGimpy)</description> 37 <param-name>kaptcha.obscurificator.impl</param-name> 38 <param-value>com.google.code.kaptcha.impl.ShadowGimpy</param-value> 39 </init-param> 40 41 <!-- 背景 --> 42 <init-param> 43 <description>背景實現類</description> 44 <param-name>kaptcha.background.impl</param-name> 45 <param-value>com.google.code.kaptcha.impl.DefaultBackground</param-value> 46 </init-param> 47 <init-param> 48 <description>背景顏色漸變,指定開始顏色</description> 49 <param-name>kaptcha.background.clear.from</param-name> 50 <param-value>yellow</param-value> 51 </init-param> 52 <init-param> 53 <description>背景顏色漸變,指定結束顏色</description> 54 <param-name>kaptcha.background.clear.to</param-name> 55 <param-value>red</param-value> 56 </init-param> 57 58 <!-- 文本 --> 59 <init-param> 60 <description>文本集合,驗證碼文字從此集合中獲取</description> 61 <param-name>kaptcha.textproducer.char.string</param-name> 62 <param-value>0123456789</param-value> 63 </init-param> 64 <init-param> 65 <description>驗證碼長度</description> 66 <param-name>kaptcha.textproducer.char.length</param-name> 67 <param-value>6</param-value> 68 </init-param> 69 <init-param> 70 <description>文字間隔</description> 71 <param-name>kaptcha.textproducer.char.space</param-name> 72 <param-value>2</param-value> 73 </init-param> 74 <!-- 字體 --> 75 <init-param> 76 <description>字體Arial,Courier</description> 77 <param-name>kaptcha.textproducer.font.names</param-name> 78 <param-value>Arial,Courier</param-value> 79 </init-param> 80 <init-param> 81 <description>字體大小</description> 82 <param-name>kaptcha.textproducer.font.size</param-name> 83 <param-value>40</param-value> 84 </init-param> 85 <init-param> 86 <description>字體顏色,white、black(默認)、blue等</description> 87 <param-name>kaptcha.textproducer.font.color</param-name> 88 <param-value>pink</param-value> 89 </init-param> 90 91 <init-param> 92 <description>文字渲染器</description> 93 <param-name>kaptcha.word.impl</param-name> 94 <param-value>com.google.code.kaptcha.text.impl.DefaultWordRenderer</param-value> 95 </init-param> 96 97 <!-- 圖片和文本的實現類 --> 98 <init-param> 99 <description>圖片實現類,可以重寫這個類實現我們自己的圖片</description> 100 <param-name>kaptcha.producer.impl</param-name> 101 <param-value>com.google.code.kaptcha.impl.DefaultKaptcha</param-value> 102 </init-param> 103 <init-param> 104 <description>文本實現類</description> 105 <param-name>kaptcha.textproducer.impl</param-name> 106 <param-value>com.google.code.kaptcha.text.impl.DefaultTextCreator</param-value> 107 </init-param> 108 109 <!-- 干擾 --> 110 <init-param> 111 <description>干擾實現類</description> 112 <param-name>kaptcha.noise.impl</param-name> 113 <param-value>com.google.code.kaptcha.impl.DefaultNoise</param-value> 114 </init-param> 115 <init-param> 116 <description>干擾顏色,合法值r,g,b或者white、black、blue</description> 117 <param-name>kaptcha.noise.color</param-name> 118 <param-value>255,0,0</param-value> 119 </init-param> 120 121 <init-param> 122 <description>session中存放驗證碼的key鍵</description> 123 <param-name>kaptcha.session.key</param-name> 124 <param-value>KAPTCHA_SESSION_KEY</param-value> 125 </init-param> 126 </servlet> 127 <servlet-mapping> 128 <servlet-name>Kcaptcha</servlet-name> 129 <url-pattern>/random.jpg</url-pattern> 130 </servlet-mapping>
中文驗證碼
查看前面的配置發現驗證碼字符的生成主要依靠的是kaptcha.textproducer.impl這個文本實現類,查看com.google.code.kaptcha.text.impl.DefaultTextCreator的源碼,發現了它繼承自 Configurable並實現了TextProducer接口。我們可以仿照它自定義我們自己的驗證碼中的文本生成器:
1 /** 2 * 中文驗證碼的實現類 3 */ 4 public class ChineseTextCreator extends Configurable implements TextProducer { 5 6 @Override 7 public String getText() { 8 9 int length = getConfig().getTextProducerCharLength(); 10 String finalWord = "", firstWord = ""; 11 int tempInt = 0; 12 String[] array = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", 13 "a", "b", "c", "d", "e", "f" }; 14 15 Random rand = new Random(); 16 17 for (int i = 0; i < length; i++) { 18 switch (rand.nextInt(array.length)) { 19 case 1: 20 tempInt = rand.nextInt(26) + 65; 21 firstWord = String.valueOf((char) tempInt); 22 break; 23 case 2: 24 int r1, 25 r2, 26 r3, 27 r4; 28 String strH, 29 strL;// high&low 30 r1 = rand.nextInt(3) + 11; // 前閉后開[11,14) 31 if (r1 == 13) { 32 r2 = rand.nextInt(7); 33 } else { 34 r2 = rand.nextInt(16); 35 } 36 37 r3 = rand.nextInt(6) + 10; 38 if (r3 == 10) { 39 r4 = rand.nextInt(15) + 1; 40 } else if (r3 == 15) { 41 r4 = rand.nextInt(15); 42 } else { 43 r4 = rand.nextInt(16); 44 } 45 46 strH = array[r1] + array[r2]; 47 strL = array[r3] + array[r4]; 48 49 byte[] bytes = new byte[2]; 50 bytes[0] = (byte) (Integer.parseInt(strH, 16)); 51 bytes[1] = (byte) (Integer.parseInt(strL, 16)); 52 53 firstWord = new String(bytes); 54 break; 55 default: 56 tempInt = rand.nextInt(10) + 48; 57 firstWord = String.valueOf((char) tempInt); 58 break; 59 } 60 finalWord += firstWord; 61 } 62 return finalWord; 63 } 64 }
只需要在web.xml中將初始化參數由默認的文本實現類改成我們自己的實現類:
1 <init-param> 2 <description>文本實現類</description> 3 <param-name>kaptcha.textproducer.impl</param-name> 4 <param-value>ChineseTextCreator</param-value> 5 </init-param>
算式驗證碼
實現步驟如下:
- 獲取隨機的數值將結果相加
- 將計算公式寫入到驗證碼圖片
- 將相加的結果放入到session中
因此,我們需要重寫KaptchaServlet這個用於生成驗證碼的Servlet。
1 public class MyKaptchaServlet extends HttpServlet implements Servlet{ 2 3 private Properties props; 4 private Producer kaptchaProducer; 5 private String sessionKeyValue; 6 7 public MyKaptchaServlet() { 8 props = new Properties(); 9 kaptchaProducer = null; 10 sessionKeyValue = null; 11 } 12 13 public void init(ServletConfig conf) throws ServletException { 14 super.init(conf); 15 16 ImageIO.setUseCache(false); 17 18 Enumeration initParams = conf.getInitParameterNames(); 19 while (initParams.hasMoreElements()) { 20 String key = (String) initParams.nextElement(); 21 String value = conf.getInitParameter(key); 22 this.props.put(key, value); 23 } 24 25 Config config = new Config(this.props); 26 this.kaptchaProducer = config.getProducerImpl(); 27 this.sessionKeyValue = config.getSessionKey(); 28 } 29 30 public void doGet(HttpServletRequest req, HttpServletResponse resp) 31 throws ServletException, IOException { 32 resp.setDateHeader("Expires", 0L); 33 34 resp.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 35 36 resp.addHeader("Cache-Control", "post-check=0, pre-check=0"); 37 38 resp.setHeader("Pragma", "no-cache"); 39 40 resp.setContentType("image/jpeg"); 41 42 String capText = this.kaptchaProducer.createText(); 43 String s1 = capText.substring(0, 1); // 獲取隨機生成的第一個數字 44 String s2 = capText.substring(1, 2); // 由於web.xml中配置的驗證碼字符都是數字,不會發生數字格式化異常 45 int r = Integer.parseInt(s1) + Integer.parseInt(s2); 46 47 req.getSession().setAttribute(this.sessionKeyValue, String.valueOf(r)); // 將結果存入session 48 49 BufferedImage bi = this.kaptchaProducer.createImage(s1 + " + " + s2 + " = ?"); // 產生圖片 50 51 ServletOutputStream out = resp.getOutputStream(); 52 53 ImageIO.write(bi, "jpg", out); 54 try { 55 out.flush(); 56 } finally { 57 out.close(); 58 } 59 } 60 61 }
在web.xml中有2點需要注意:
- 驗證碼文本只能是0~9這10個數字;
- 驗證碼的長度是2(寫多了也只會取出前2個隨機數,寫少了會拋出數字格式化異常)。
1 <servlet-name>Kcaptcha</servlet-name> 2 <servlet-class>MyKaptchaServlet</servlet-class> 3 <init-param> 4 <description>文本集合</description> 5 <param-name>kaptcha.textproducer.char.string</param-name> 6 <param-value>0123456789</param-value> 7 </init-param> 8 <init-param> 9 <description>驗證碼長度</description> 10 <param-name>kaptcha.textproducer.char.length</param-name> 11 <param-value>8</param-value> 12 </init-param>