javaweb之验证码
看了尚硅谷佟刚老师讲的httpsession应用中的验证码,总觉得一遍又一遍的写这些东西实在没意思,所有就简单封装了一个验证码生成器,默认支持纯数字、纯字母、数字字母组合、简单的10以内的加减乘的验证码。
先声明:代码中BufferedImage等的生成部分和一些对验证码的修饰都是来自佟刚老师的代码!
下载jar包:
https://files.cnblogs.com/huyongliang/codeGenerator.zip
来个截图:
一 结构概览
CheckCode类是这个工具包的抽象父类,提供了两个默认实现:
1) DefaultCheckCode:支持数字、字母和数字加字母的验证码
2) SimpleArithmeticExpressionCheckCode:简单的10以内加减乘的验证码
二 代码
1. 抽象父类DefaultCheckCode.java
这个类稍微有点长,其outline视图如下:
对调用者提供的接口只有两个:
1)getCheckCode(): public BufferedImage getCheckCode(HttpServletRequest request)
返回生成的代表验证码的BufferedImage对象(并将正确的验证码写入session,键就是CHECK_CODE_KEY,可以自定义),拿到他之你就你就可以发送给客户端。
2)isValid(): public static boolean isValid(HttpServletRequest request,String clientAnswer)
该方法用于判断客户端请求中的验证码是否合法。
全部代码如下(这是个抽象类,默认实现在后面,你可以自己实现他):
1 package org.hyl.utils.code; 2 3 import java.awt.Color; 4 import java.awt.Font; 5 import java.awt.Graphics2D; 6 import java.awt.image.BufferedImage; 7 import java.util.Random; 8 9 import javax.servlet.http.HttpServletRequest; 10 import javax.servlet.http.HttpSession; 11 12 public abstract class CheckCode { 13 protected static String CHECK_CODE_KEY = "CHECK_CODE_KEY"; 14 15 protected int width = 152; 16 protected int height = 40; 17 protected int codeCount = 4; 18 19 // 验证码字体的高度 20 protected int fontHeight = 4; 21 22 // 验证码中的单个字符基线. 即:验证码中的单个字符位于验证码图形左上角的 (codeX, codeY) 位置处 23 protected int codeX = 0; 24 protected int codeY = 0; 25 26 27 28 public CheckCode() { 29 this.width = 152; 30 this.height = 40; 31 this.init(); 32 } 33 34 private void init() { 35 fontHeight = height - 2; 36 codeX = width / (codeCount + 2); 37 codeY = height - 4; 38 } 39 40 protected abstract String getPrintString(); 41 42 protected abstract String getRightAnswer(); 43 44 public BufferedImage getCheckCode(HttpServletRequest request) { 45 this.init(); 46 char[] codeSequence = this.getPrintString().toCharArray(); 47 int len = codeSequence.length; 48 BufferedImage image = new BufferedImage(width, height, 49 BufferedImage.TYPE_3BYTE_BGR); 50 51 this.renderImg(image); 52 53 Graphics2D graphics = image.createGraphics(); 54 this.setDefaultStyleOfCode(graphics); 55 56 for (int i = 0; i < len; i++) { 57 graphics.setColor(getRandomColor()); 58 graphics.drawString(String.valueOf(codeSequence[i]), (i + 1) 59 * codeX, codeY); 60 } 61 String answer = this.getRightAnswer(); 62 63 request.getSession().setAttribute(CHECK_CODE_KEY, answer); 64 65 return image; 66 } 67 68 protected Color getRandomColor() { 69 Random random = new Random(); 70 71 Color c = new Color(random.nextInt(154) + 50, random.nextInt(154) + 50, 72 random.nextInt(154) + 50); 73 return c; 74 } 75 76 protected void renderImg(BufferedImage image) { 77 Graphics2D graphics = image.createGraphics(); 78 79 // 设置一个颜色, 使 Graphics2D 对象的后续图形使用这个颜色 80 graphics.setColor(Color.WHITE); 81 82 // 填充一个指定的矩形: x - 要填充矩形的 x 坐标; y - 要填充矩形的 y 坐标; width - 要填充矩形的宽度; height 83 // - 要填充矩形的高度 84 graphics.fillRect(0, 0, width, height); 85 86 // 创建一个 Font 对象: name - 字体名称; style - Font 的样式常量; size - Font 的点大小 87 Font font = null; 88 font = new Font("", Font.BOLD, fontHeight); 89 // 使 Graphics2D 对象的后续图形使用此字体 90 graphics.setFont(font); 91 92 graphics.setColor(Color.BLACK); 93 94 // 绘制指定矩形的边框, 绘制出的矩形将比构件宽一个也高一个像素 95 graphics.drawRect(0, 0, width - 1, height - 1); 96 97 // 随机产生 15 条干扰线, 使图像中的认证码不易被其它程序探测到 98 Random random = null; 99 random = new Random(); 100 graphics.setColor(Color.GREEN); 101 for (int i = 0; i < 15; i++) { 102 int x = random.nextInt(width); 103 int y = random.nextInt(height); 104 int x1 = random.nextInt(20); 105 int y1 = random.nextInt(20); 106 graphics.drawLine(x, y, x + x1, y + y1); 107 } 108 } 109 110 protected void setDefaultStyleOfCode(Graphics2D graphics) { 111 Font font = null; 112 font = new Font("", Font.BOLD, fontHeight); 113 // 使 Graphics2D 对象的后续图形使用此字体 114 graphics.setFont(font); 115 } 116 117 public enum CODE_TYPE { 118 DIGIT, LETTER, DIGIT_LETTER 119 } 120 121 public static boolean isValid(HttpServletRequest request, 122 String clientAnswer) { 123 if (clientAnswer == null || clientAnswer.equals("")) 124 return false; 125 HttpSession sessioin = request.getSession(false); 126 if (sessioin == null) 127 return false; 128 129 String code = (String) sessioin.getAttribute(CHECK_CODE_KEY); 130 if (code == null || code == "") 131 return false; 132 if (code.equalsIgnoreCase(clientAnswer)) { 133 sessioin.removeAttribute(CHECK_CODE_KEY); 134 return true; 135 } 136 return false; 137 } 138 }
2. 默认实现类DefaultCheckCode.java(数字、字母、数字加字母)
1 package org.hyl.utils.code.iml; 2 import java.util.Random; 3 import org.hyl.utils.code.CheckCode; 4 public class DefaultCheckCode extends CheckCode { 5 6 private char[] codeSequence; 7 private String rightAnswer; 8 9 public DefaultCheckCode(CODE_TYPE codeType) { 10 switch (codeType) { 11 case DIGIT: 12 this.codeSequence = "0123456789".toCharArray(); 13 break; 14 case LETTER: 15 this.codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 16 .toCharArray(); 17 break; 18 case DIGIT_LETTER: 19 this.codeSequence = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz23456789" 20 .toCharArray(); 21 break; 22 default: 23 break; 24 } 25 } 26 27 public DefaultCheckCode(String userCodeSequence) { 28 if (userCodeSequence == null || userCodeSequence.equals("")) 29 throw new RuntimeException("用于生产验证码的序列不合法"); 30 this.codeSequence = userCodeSequence.toCharArray(); 31 } 32 33 public DefaultCheckCode(char[] codeSequence) { 34 this.codeSequence = codeSequence; 35 } 36 37 @Override 38 protected String getPrintString() { 39 Random random = new Random(); 40 StringBuilder builder = new StringBuilder(); 41 int len = this.codeSequence.length; 42 for (int i = 0; i < this.codeCount; i++) { 43 builder.append(this.codeSequence[random.nextInt(len)]); 44 } 45 this.rightAnswer = builder.toString(); 46 return builder.toString(); 47 } 48 49 @Override 50 protected String getRightAnswer() { 51 if (this.rightAnswer == null || this.rightAnswer.equals("")) 52 this.getPrintString(); 53 return this.rightAnswer; 54 } 55 56 //以下三个方法可用于链式编程 57 public DefaultCheckCode setWidth(int width) { 58 this.width = width; 59 return this; 60 } 61 62 public DefaultCheckCode setHeight(int height) { 63 this.height = height; 64 return this; 65 } 66 67 public DefaultCheckCode setCodeCount(int c) { 68 this.codeCount = c; 69 return this; 70 } 71 }
3. 另一个实现类SimpleArithmeticExpressionCheckCode(简单数学表达式)
1 package org.hyl.utils.code.iml; 2 3 import java.util.Random; 4 5 import org.hyl.utils.code.CheckCode; 6 7 public class SimpleArithmeticExpressionCheckCode extends CheckCode { 8 private String printString; 9 private String rightAnswer; 10 11 private int num1; 12 private int num2; 13 private char op; 14 15 public SimpleArithmeticExpressionCheckCode() { 16 17 this.initOperationParams(); 18 this.printString = new StringBuilder().append(num1).append(op) 19 .append(num2).append("=?").toString(); 20 this.rightAnswer = this.executeCalculte() + ""; 21 } 22 23 private void initOperationParams() { 24 Random random = new Random(); 25 this.op = (new char[] { 'x', '-', '+', 'X' })[random.nextInt(4)]; 26 this.num1 = random.nextInt(10); 27 this.num2 = random.nextInt(10); 28 } 29 30 private int executeCalculte() { 31 switch (this.op) { 32 case '+': 33 return this.num1 + this.num2; 34 case '-': 35 return this.num1 - this.num2; 36 case 'x': 37 case 'X': 38 return this.num1 * this.num2; 39 default: 40 return 0; 41 } 42 } 43 44 @Override 45 protected String getPrintString() { 46 return this.printString; 47 } 48 49 @Override 50 protected String getRightAnswer() { 51 return rightAnswer; 52 } 53 54 }
三 生成验证码用法示例
大多数的使用可能都是在控制器servlet中的,至于框架的话,我想你也会搞定。
1 package org.hyl.servlet; 2 3 import java.awt.image.BufferedImage; 4 import java.io.IOException; 5 6 import javax.imageio.ImageIO; 7 import javax.servlet.ServletException; 8 import javax.servlet.ServletOutputStream; 9 import javax.servlet.http.HttpServlet; 10 import javax.servlet.http.HttpServletRequest; 11 import javax.servlet.http.HttpServletResponse; 12 13 import org.hyl.utils.code.CheckCode; 14 import org.hyl.utils.code.CheckCode.CODE_TYPE; 15 import org.hyl.utils.code.iml.DefaultCheckCode; 16 17 public class GerCheckCodeServlet extends HttpServlet { 18 private static final long serialVersionUID = 1L; 19 20 protected void doGet(HttpServletRequest request, 21 HttpServletResponse response) throws ServletException, IOException { 22 // 拿到验证码类的实现类的对象 23 24 // ==================start===================== 25 //DIGIT_LETTER-----数字加字母 26 CheckCode checkCode = new DefaultCheckCode(CODE_TYPE.DIGIT_LETTER); 27 // ==================end===================== 28 29 30 // 获得将要发往Client的BufferedImage对象 31 BufferedImage image = checkCode.getCheckCode(request); 32 33 // 禁止图像缓存 34 response.setHeader("Pragma", "no-cache"); 35 response.setHeader("Cache-Control", "no-cache"); 36 response.setDateHeader("Expires", 0); 37 38 // 将图像输出到输出流中 39 ServletOutputStream sos = null; 40 sos = response.getOutputStream(); 41 ImageIO.write(image, "jpeg", sos); 42 sos.close(); 43 } 44 45 protected void doPost(HttpServletRequest request, 46 HttpServletResponse response) throws ServletException, IOException { 47 this.doGet(request, response); 48 } 49 50 }
无论使用哪个实现类,直接将上一个代码块的注释中的start到end部分替换即可。
1. DefaultCheckCode
上面一个代码块是数字加字母的验证码的使用。
对于DefaultCheckCode这个实现类的使用,你可以看看他的构造方法:
public DefaultCheckCode(CODE_TYPE codeType){ //.... } public DefaultCheckCode(String userCodeSequence) { //.... } public DefaultCheckCode(char[] codeSequence) { //... }
其他提供的构造的使用如下(直接将上一个代码块的注释start到end部分替换即可):
以下的前三个方法都需传入一个在父类中定义的枚举类型:
1 public enum CODE_TYPE { 2 DIGIT,//纯数字 3 LETTER,//纯字母 4 DIGIT_LETTER//数字加字母 5 }
1)纯数字: CheckCode checkCode = new DefaultCheckCode(CODE_TYPE.DIGIT);
2)纯字母: CheckCode checkCode = new DefaultCheckCode(CODE_TYPE.LETTER);
3)数字字母组合: CheckCode checkCode = new DefaultCheckCode(CODE_TYPE.DIGIT_LETTER);
4)自定义验证码序列来源: CheckCode checkCode = new DefaultCheckCode("12580ABC"); 从1、2、5、8、0、A、B、C中随机选择生成验证码。
5)自定义验证码序列来源: CheckCode checkCode = new DefaultCheckCode(new char[]{'a','C','1'}); 从a、C、1中随机选择生成验证码。
6)可以自己定义验证码的宽度高度和个数的(有点链式编程的风格):
/*创建对象并封盖默认的高度宽度和字符个数*/ CheckCode checkCode = new DefaultCheckCode(CODE_TYPE.DIGIT) .setCodeCount(6) .setHeight(50) .setWidth(140);
2.SimpleArithmeticExpressionCheckCode(或许这才是你感兴趣的)
只有一个无参的构造给你用: CheckCode checkCode = new SimpleArithmeticExpressionCheckCode();
四 验证Client发送的验证码是否正确
验证成功则返回true,并将session中的键为CHECK_CODE_KEY的属性移除
表单:
1 <form action="<%=request.getContextPath()%>/GerCheckCodeServlet"> 2 UserName:<input type="text" name="userName"> 3 <br> 4 CheckCode:<input type="text" name="inputCode"> 5 <img src="<%=request.getContextPath()%>/ValidateColorServlet"> 6 <br> 7 <input type="submit" value="submit"> 8 </form>
用于验证验证码正确性的servlet
1 protected void doGet(HttpServletRequest request, 2 HttpServletResponse response) throws ServletException, IOException { 3 String inputCode = request.getParameter("inputCode"); 4 boolean isValid = CheckCode.isValid(request, inputCode); 5 if (isValid) {// 验证成功 6 //.... 7 } else {// 验证失败 8 //.... 9 } 10 }
五 说明
1. 对于自己写实现的说明:
你可以很容易实现你自己的实现类,只需要实现两个方法:
这个方法让你提供一个已经生成的验证码序列,就是将要写到BufferedImage中的验证码字符串。 protected abstract String getPrintString(); 这个方法是让你提供验证码的正确答案,当然,对应传统的验证码,比如DefaultCheckCode,看到的就是正确答案;
但是对应项数学表达式的话,这个方法应该返回一个计算结果比如1+2=3,你该返回3。getPrintString应该返回1+2。 protected abstract String getRightAnswer();
2.有一个工具类供你使用
如果你实在是懒得不行的话,那么这里有个工具类让你使用:
发送验证码到客户端: CheckCodeUtil.sendCheckCodeToClient(request, response, new SimpleArithmeticExpressionCheckCode()); 用这一行代码你就能发送一个验证码到客户端了。
代码如下:
1 package org.hyl.utils.code; 2 3 import java.awt.image.BufferedImage; 4 import java.io.IOException; 5 6 import javax.imageio.ImageIO; 7 import javax.servlet.ServletOutputStream; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 /** 12 * 针对于{@link CheckCode}的实现类的工具类 13 * 14 * @author HuYongliang 15 * 16 */ 17 public class CheckCodeUtil { 18 /** 19 * 发送参数code生产的验证码到response对应的客户端,并将验证码加入session中 20 * 21 * @param request 22 * @param response 23 * @param code 24 */ 25 public static void sendCheckCodeToClient(HttpServletRequest request, 26 HttpServletResponse response, CheckCode code) { 27 BufferedImage image = code.getCheckCode(request); 28 // 禁止图像缓存 29 response.setHeader("Pragma", "no-cache"); 30 response.setHeader("Cache-Control", "no-cache"); 31 response.setDateHeader("Expires", 0); 32 33 // 将图像输出到输出流中 34 ServletOutputStream sos = null; 35 try { 36 sos = response.getOutputStream(); 37 ImageIO.write(image, "jpeg", sos); 38 } catch (IOException e) { 39 e.printStackTrace(); 40 } finally { 41 try { 42 sos.close(); 43 } catch (IOException e) { 44 e.printStackTrace(); 45 } 46 } 47 } 48 49 /** 50 * 判断client传送的验证码是否正确 51 * 52 * @param request 53 * @param clientAnswer 54 * @param checkCode 55 * @return 56 */ 57 public static boolean isValid(HttpServletRequest request, 58 String clientAnswer, CheckCode checkCode) { 59 return CheckCode.isValid(request, clientAnswer); 60 } 61 62 /** 63 * 判断client传送的验证码是否正确 64 * 65 * @param request 66 * @param clientAnswer 67 * @return 68 */ 69 public static boolean isValid(HttpServletRequest request, 70 String clientAnswer) { 71 return CheckCode.isValid(request, clientAnswer); 72 } 73 }
好了,不怎么会排版,有点乱。
欢迎纠错。