Java实现验证码制作

Java实现验证码制作


java.jpg

第一章 概述

1.1 验证码概述

  • 为什么要使用验证码
  • 什么是验证码
  • 使用Servlet实现验证码
  • 使用开源组件实现验证码
  • 验证码的发展

如图可以发现计算机也可以通过传递URL参数等来执行登录操作

没有验证码带来的问题

  • 对特定用户不断登录破解密码
  • 对某个网站创建账户
  • 对某个网站提交垃圾数据
  • 对某个网站刷票

我们要通过验证码, 由用户肉眼识别其中的验证码信息, 从而区分用户是人还是计算机

验证码的定义

  • 验证码(captcha):是一种区分用户是人还是计算机的公共全自动程序.
  • 作用:可以防止恶意破解密码,刷票,论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试.
  • 实际上用验证码是现在很多网站通行的方法,我们利用比较简易的方式实现了这个功能.

第二章 使用Servlert实现验证码

2.1 使用Servlert实现验证码的步骤

验证码包含两部分:

  1. 输入框
  2. 显示验证码的图片
    那么验证码图片是如何获取的?

网页显示验证码

  • <input type="text" id="verifyCode" name="verifyCode" size=6"" />
  • <img alt="验证码" id="safecode" src=<%=request.getContextPath()%>/servlet/ImageServlet">

生成图片

  • 生成图片用到的类
  1. BufferedImage图像数据缓冲区
  2. Graphics绘制图片
  3. Color获取颜色
  4. Random生成随机数
  5. ImageIO输出图片

生成图片的实现类

  • ImageServlet类
  1. 定义BufferedImage对象
  2. 获得Graphics对象
  3. 通过Random产生随机验证码信息
  4. 使用Graphics绘制图片
  5. 记录验证码信息到session中
  6. 使用ImageIO输出图片

校验验证码是否正确

  • LoginServlet类
  1. 获取页面验证码
  2. 获取session保存的验证码
  3. 比较验证码
  4. 返回校验结果

使用Servlet实现验证码流程


2.2 验证码的代码实现

    // /servlet/ImageServlet
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
        BufferedImage bi = new BufferedImage(68, 22, BufferedImage.TYPE_INT_RGB);
        Graphics gs = bi.getGraphics();
        Color c = new Color(196, 162, 129);
        gs.setColor(c);
        gs.fillRect(0,0,68,22);

        char[] ch = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray();
        Random ran = new Random();
        int len = ch.length,index;
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 4; i++) {
            index = ran.nextInt(len);
            gs.setColor(new Color(Test.getCorrectColor(196),Test.getCorrectColor(162),Test.getCorrectColor(129)));
            gs.drawString(ch[index] + "", (15 * i) + 8, 18);
            sb.append(ch[index]);
        }
        request.getSession().setAttribute("piccode", sb.toString());
        ImageIO.write(bi, "JPG", response.getOutputStream());
    }
    
    //Test.java
    public static int getCorrectColor(int a){
        Random ran = new Random();
        int temp = ran.nextInt(256);
        if (0 <= a - temp && a - temp < 40) {
            temp = a - 40;
        }
        if (a - temp < 0 && temp - a < 40) {
            temp = a + 40;
        }
        return temp;
    }

index.jsp

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>验证码制作</title>
      <script type="text/javascript">
          function flush_() {
              document.getElementById("captcha").src = "<%=request.getContextPath()%>/servlet/ImageServlet?d="+new Date();
              alert("88");
          }
      </script>
    </head>
    <body>
    验证码: <input type="text" name="captcha" />
    <img alt="captcha" id="captcha" src="<%=request.getContextPath()%>/servlet/ImageServlet" />
    <a href="javascript:
    (function(){document.getElementById('captcha').src='<%=request.getContextPath()%>/servlet/ImageServlet?d='+new Date();})();">看不清楚</a>
    </body>
    </html>
    <%--这里提供了两种js函数定义方法, 其中匿名函数的定义方式与java并不相同.
    这里采用的是(function(a){...})(a)这种格式.前一个()为方法体的定义, 后一个()表示传参并执行.
    匿名函数的缺点在与对一些需要重新解除函数绑定的事件类动作无法解绑?个人理解, 并不懂.
    同时, 在方法体中由于不能使用""进行书写, 则选择使用''代替, 或者使用"字符转义代替.
    在方法体末端的"?d=+"new Date()", 表示通过当前时间来改变网页请求, 从而实现刷新.
    否则浏览器缓存的原因会导致不刷新.
    --%>

2.3 验证码的校验

index.jsp

<form action="<%=request.getContextPath()%>/servlet/LoginServlet" method="post">
    验证码: <input type="text" name="captchaNo" />
    <img alt="captcha" id="captcha" src="<%=request.getContextPath()%>/servlet/ImageServlet" />
    <a href="javascript:(function(){document.getElementById('captcha').src='<%=request.getContextPath()%>/servlet/ImageServlet?d='+new Date();})();">看不清楚</a>
    <div><input type="submit" value="提交" /></div>
</form>

LoginServlet.java

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    String captcha = request.getParameter("captchaNo").toUpperCase();
    System.out.println(captcha);
    System.out.println(request.getSession().getAttribute("piccode"));
    if (request.getSession().getAttribute("piccode").equals(captcha)) response.sendRedirect("/success.jsp");
    else response.sendRedirect("/index.jsp");
    System.out.println("验证完成===========");
}

Test.java

public static int getCorrectColor(int a){
    Random ran = new Random();
    int i = ran.nextInt(41) + (255 - a - 20);
    i = i > 0 ? i : 0;
    i = i > 255 ? 255 : i;
    System.out.print(i+" ");
    return i;
}

第三章 使用Jcaptcha组件实现验证码

  • Jcaptcha: 一个用来生成图形验证码的Java开源组件,使用非常简单方便. 与Spring组合使用,可产生多种形式的验证码.
  • Kaptcha: 一个非常使用的验证码生成工具, 它是可以配置的, 可以生成各种各样的验证码.

Jcaptcha组件实现验证码实例

项目案例

  • JSP禁用缓存的方式 response.setHeader( "Pragma", "no-cache" ); setDateHeader("Expires", 0);的用法和什么意思

JSP禁用缓存的方式 使用服务器端控制AJAX页面缓存: response.setHeader( "Pragma", "no-cache" ); response.addHeader( "Cache-Control", "must-revalidate" ); response.addHeader( "Cache-Control", "no-cache" ); response.addHeader( "Cache-Control", "no-store" ); response.setDateHeader("Expires", 0); 单纯的使用 xmlhttp.setRequestHeader("Cache-Control","no-cache")无效。

Cache-Control头域 Cache-Control指定请求和响应遵循的缓存机制。 在请求消息或响应消息中设置Cache-Control并不会修改另一个消息处理过程中的缓存处理过程。 请求时的缓存指令包括no-cache、no-store、max-age、max-stale、min-fresh、only-if-cached. 响应消息中的指令包括public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age。 各个消息中的指令含义如下: Public指示响应可被任何缓存区缓存。   Private指示对于单个用户的整个或部分响应消息,不能被共享缓存处理。这允许服务器仅仅描述当用户的部分响应消息,此响应消息对于其他用户的请求无效。   no-cache指示请求或响应消息不能缓存   no-store用于防止重要的信息被无意的发布。在请求消息中发送将使得请求和响应消息都不使用缓存。   max-age指示客户机可以接收生存期不大于指定时间(以秒为单位)的响应。   min-fresh指示客户机可以接收响应时间小于当前时间加上指定时间的响应。   max-stale指示客户机可以接收超出超时期间的响应消息。如果指定max-stale消息的值,那么客户机可以接收超出超时期指定值之内的响应消息。

禁用IE缓存 HTTP消息报头包括普通报头、请求报头、响应报头、实体报头。 普通报头中的Cache-Control用于指定缓存指令,缓存指令是单向的(响 应中出现的缓存指令在请求中未必会出现).且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制),HTTP1.0使用的类似的报头域为Pragma。 请求时的缓存指令包括:no-cache(用于指示请示或响应消息不能缓存)、no-store、max-age、max-stale、min-fresh、only-if-cached; 响应时的缓存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage。

例:为了指示IE浏览器(客户端)不要缓存页面,服务器端的jsp程序可以编写如下: response.setHeader(“Cache-Control”, “no-cache”); //response.setHeader(“Pragma”, “no-cache”);作用相当于上行代码,通常两者合用

Expires实体报头域给出响应过期的日期和时间。 为了让代理服务器或浏览器在一段时间以后更新缓存中(再次访问曾访问过的页面时,直接从缓存中加载,缩短响应时间和降低服务器负载)的页面, 我们可以使用Expires实体报头域指定页面过期时间。 例:Expires:Thu,15 Sep 2006 16:23:12 GMT HTTP1.1的客户端和缓存必须将其他非法的日期格式(包括0)看作已经过期。如:为了让浏览器不要缓存页面,也可以利用Expires实体报关域,设置为0,jsp程序如下: response.setDateHeader(“Expires”, “0”);

jcaptcha使用默认样式生成的验证码比较难以识别,所以需要自定义验证码的样式,包括,背景色、背景大小、字体、字体大小、生成的字符数等。相对与kaptcha比较复杂。

纯代码实现jcaptcha验证码

1、首先创建一个javaWeb工程添加jcaptcha包和它所依赖的包.

commons-collections-3.2.jar
commons-logging-1.2.jar
jcaptcha-1.0-all.jar

2、创建一个jcaptcha单例的Service类,这里是设置验证码样式的关键部分,代码如下:

package cn.v5cn.jcaptchatest.custom;
 
import java.awt.Font;
 
import com.octo.captcha.CaptchaFactory;
import com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundGenerator;
import com.octo.captcha.component.image.color.RandomRangeColorGenerator;
import com.octo.captcha.component.image.fontgenerator.RandomFontGenerator;
import com.octo.captcha.component.image.textpaster.RandomTextPaster;
import com.octo.captcha.component.image.wordtoimage.ComposedWordToImage;
import com.octo.captcha.component.word.FileDictionary;
import com.octo.captcha.component.word.wordgenerator.ComposeDictionaryWordGenerator;
import com.octo.captcha.engine.GenericCaptchaEngine;
import com.octo.captcha.image.gimpy.GimpyFactory;
import com.octo.captcha.service.image.ImageCaptchaService;
import com.octo.captcha.service.multitype.GenericManageableCaptchaService;
 
public class CaptchaServiceSingleton {
    private static ImageCaptchaService service = null;
 
    public static ImageCaptchaService getService(){
        if(service == null)
            service = generatorCaptchaService();
        return service;
    }
    /**
     * 根据SpringBean的配置文件编写的代码实现
     * */
    public static ImageCaptchaService generatorCaptchaService(){
        //生成随机颜色,参数分别表示RGBA的取值范围
        RandomRangeColorGenerator textColor = new RandomRangeColorGenerator(new int[]{0,255},new int[]{0,180},new int[]{0,210},new int[]{255,255});
        //随机文字多少和颜色,参数1和2表示最少生成多少个文字和最多生成多少个
        RandomTextPaster randomTextPaster = new RandomTextPaster(4, 5, textColor,true);
        //生成背景的大小这里是宽85高40的图片,也可以设置背景颜色和随机背景颜色,这里使用默认的白色
        UniColorBackgroundGenerator colorbgGen = new UniColorBackgroundGenerator(85,40);
        //随机生成的字体大小和字体类型,参数1和2表示最小和最大的字体大小,第三个表示随机的字体
        RandomFontGenerator randomFontGenerator = new RandomFontGenerator(20, 25, new Font[]{new Font("Arial", 0, 10),new Font("Tahoma",0,10)});
        //结合上面的对象构件一个从文本生成图片的对象
        ComposedWordToImage cwti = new ComposedWordToImage(randomFontGenerator,colorbgGen,randomTextPaster);
        //随机文本的字典,这里是使用jcaptcha-1.0-all.jar中的文本字典,字典名称为toddlist.properties
        ComposeDictionaryWordGenerator cdwg = new ComposeDictionaryWordGenerator(new FileDictionary("toddlist"));
 
        GimpyFactory gf = new GimpyFactory(cdwg, cwti);
 
        GenericCaptchaEngine gce = new GenericCaptchaEngine(new CaptchaFactory[]{gf});
        //返回一个Service对象,这里180是验证码存在的时间,单位是秒,200000是最大存储大小
        return new GenericManageableCaptchaService(gce,180,200000,75000);
    }
}

3、创建一个生成验证码的Servlet,代码如下:

package cn.v5cn.jcaptchatest.custom;
 
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
 
public class CustomServlet extends HttpServlet {
 
    private static final long serialVersionUID = 169310818225761427L;
     
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        byte[] captChallengeAsJpeg = null;
         
        ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
         
        String captchaId = req.getSession().getId();
        BufferedImage challenge = CaptchaServiceSingleton.getService().getImageChallengeForID(captchaId,req.getLocale());
         
        JPEGImageEncoder jpegEncoder = JPEGCodec.createJPEGEncoder(jpegOutputStream);
        jpegEncoder.encode(challenge);
         
        captChallengeAsJpeg = jpegOutputStream.toByteArray();
         
         resp.setHeader("Cache-Control", "no-store");
         resp.setHeader("Pragma", "no-cache");
         resp.setDateHeader("Expires", 0);
         resp.setContentType("image/jpeg");
          
         ServletOutputStream respOutputStream = resp.getOutputStream();
         respOutputStream.write(captChallengeAsJpeg);
         respOutputStream.flush();
         respOutputStream.close();
    }
}

4、在Web.xml中注册这个Servlet,代码如下:

<servlet>
    <servlet-name>generateValidateCode</servlet-name>
    <servlet-class>cn.v5cn.jcaptchatest.custom.CustomServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>generateValidateCode</servlet-name>
    <url-pattern>/cgvc</url-pattern>
  </servlet-mapping>

5、编写一个使用验证码的页面,代码如下:

<form action="valiServlet" method="post">
    <input type="text" name="customgvc">
    ![](cgvc)
    <input type="submit" value="提交">
</form>

6、编写一个验证Servlet并在web.xml中注册,代码分别如下:

package cn.v5cn.jcaptchatest.custom;
 
import java.io.IOException;
 
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
public class ValidateServlet extends HttpServlet {
 
    private static final long serialVersionUID = -7173743572400866269L;
     
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        String captchaId = req.getSession().getId();
         
        String validateCode = req.getParameter("customgvc");
         
        boolean validateResult = CaptchaServiceSingleton.getService().validateResponseForID(captchaId, validateCode);
        if(validateResult)
            resp.sendRedirect("success.html");
        else
            resp.sendRedirect("fail.html");
    }
}
<servlet>
    <servlet-name>validatServlet</servlet-name>
    <servlet-class>cn.v5cn.jcaptchatest.custom.ValidateServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>validatServlet</servlet-name>
    <url-pattern>/valiServlet</url-pattern>
  </servlet-mapping>

第四章 使用Kaptcha组件实现验证码制作

4.1 字母数字组合验证码的实现

web.xml

    <!--kaptcha验证码配置-->
    <servlet>
        <servlet-name>kaptcha</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
        <init-param>
            <param-name>kaptcha.border</param-name>
            <param-value>no</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.textproducer.font.color</param-name>
            <param-value>black</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.textproducer.char.space</param-name>
            <param-value>5</param-value>
        </init-param>
    </servlet>

    <servlet-mapping>
        <servlet-name>kaptcha</servlet-name>
        <url-pattern>/kaptcha.jpg</url-pattern>
    </servlet-mapping>
    <!--
    kaptcha.border:是否显示边框。
    kaptcha.textproducer.font.color:字体颜色
    kaptcha.textproducer.char.space:字符间距
    更多的属性设置可以在com.google.code.kaptcha.Constants类中找到。其中包括:
    kaptcha.border.color:边框颜色
    kaptcha.border.thickness:边框宽度
    kaptcha.textproducer.char.length:产生字符的长度
    kaptcha.textproducer.font.size:产生字符的大小
    kaptcha.image.width:产生图片的宽度
    kaptcha.image.height:产生图片的高度
    -->

index.jsp

    <form action="${pageContext.request.contextPath}/kaptcha" method="post">
        <input type="text" name="kaptchaValidate">
        <img id="vcode"onclick="(function() {document.getElementById('vcode').src
        ='${pageContext.request.contextPath}/kaptcha.jpg?'+new Date();

        })()" title="点击更换" style="vertical-align: middle;" alt="验证图片"
        src="${pageContext.request.contextPath}/kaptcha.jpg" height="35" width="80">
        <input type="submit" value="提交">
    </form>

KaptchaValidateServlet.java

    protected void doPost(HttpServletRequest request, HttpServletResponse response) 
    throws ServletException, IOException {
        String validate = request.getParameter("kaptchaValidate");
        String validateCode = (String) request.getSession().getAttribute(Constants.KAPTCHA_SESSION_KEY);

        if (validate ==null || validateCode == null) response.sendRedirect("/index.jsp");
        else if (!validateCode.equals(validate)) response.sendRedirect("/index.jsp");
        else response.sendRedirect("/success.jsp");
    }

4.2 Kaptcha的详细配置

<!--kaptcha验证码配置-->
    <servlet>
        <servlet-name>kaptcha</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
        <init-param>
            <description>图片边框, yes or no</description>
            <param-name>kaptcha.border</param-name>
            <param-value>yes</param-value>
        </init-param>
        <init-param>
            <description>边框颜色, white,black,blue...</description>
            <param-name>kaptcha.border.color</param-name>
            <param-value>blue</param-value>
        </init-param>
        <init-param>
            <description>边框厚度,正整数</description>
            <param-name>kaptcha.border.thickness</param-name>
            <param-value>2</param-value>
        </init-param>
<!--        <init-param>
            <description>图片宽度</description>
            <param-name>kaptcha.image.width</param-name>
            <param-value>80</param-value>
        </init-param>
        <init-param>
            <description>图片高度</description>
            <param-name>kaptcha.image.height</param-name>
            <param-value>35</param-value>
        </init-param>-->
<!--       <init-param>
            <description>图片实现类</description>
            <param-name>kaptcha.producer.impl</param-name>
            <param-value>com.google.code.kaptcha.impl.DefaultKaptcha</param-value>
        </init-param>-->
        <init-param>
            <description>文本实现类, 类似文本字符集</description>
            <param-name>kaptcha.textproducer.impl</param-name>
            <param-value>ArithmeticIdentify</param-value>
            <!--<param-value>ChineseTest</param-value>-->
        </init-param>
<!--        <init-param>
            <description>文本集合,验证码值从此集合中获取</description>
            <param-name>kaptcha.textproducer.char.string</param-name>
            <param-value>我们就是这样实现验证码制作然后怎么呢文本类似大小设置长度提交进行尽兴金星影响营养</param-value>
        </init-param>-->
        <init-param>
            <description>设置字体</description>
            <param-name>kaptcha.textproducer.font.names</param-name>
            <param-value>Sim Sun</param-value>
        </init-param>
<!--        <init-param>
            <description>设置字体大小</description>
            <param-name>kaptcha.textproducer.font.size</param-name>
            <param-value>16</param-value>
        </init-param>-->
        <init-param>
            <description>字体颜色</description>
            <param-name>kaptcha.textproducer.font.color</param-name>
            <param-value>blue</param-value>
        </init-param>
        <init-param>
            <description>文字间隔</description>
            <param-name>kaptcha.textproducer.char.space</param-name>
            <param-value>10</param-value>
        </init-param>
<!--        <init-param>
            <description>干扰实现类</description>
            <param-name>kaptcha.noise.impl</param-name>
            <param-value>com.google.code.kaptcha.impl.DefaultNoise</param-value>
        </init-param>-->
        <init-param>
            <description>干扰颜色</description>
            <param-name>kaptcha.noise.color</param-name>
            <param-value>yellow</param-value>
        </init-param>
<!--        <init-param>
            <description>背景实现类</description>
            <param-name>kaptcha.background.impl</param-name>
            <param-value>com.google.code.kaptcha.impl.DefaultBackground</param-value>
        </init-param>-->
        <init-param>
            <description>背景颜色渐变, 开始颜色</description>
            <param-name>kaptcha.background.clear.from</param-name>
            <param-value>green</param-value>
        </init-param>
        <init-param>
            <description>背景颜色渐变, 结束颜色</description>
            <param-name>kaptcha.background.clear.to</param-name>
            <param-value>white</param-value>
        </init-param>
<!--        <init-param>
            <description>文字渲染器</description>
            <param-name>kaptcha.word.impl</param-name>
            <param-value>com.google.code.kaptcha.text.impl.DefaultWordRenderer</param-value>
        </init-param>-->
<!--       <init-param>
            <description>图片样式: FishEyeGimpy鱼眼,WaterRipple水纹,ShadowGimpy阴影,默认水纹</description>
            <param-name>kaptcha.obscurificator.impl</param-name>
            <param-value>com.google.code.kaptcha.impl.ShadowGimpy</param-value>
        </init-param>-->
 <!--       <init-param>
            <description>session中存放验证码的key键</description>
            <param-name>kaptcha.session.key</param-name>
            <param-value>KAPTCHA_SESSION_KEY</param-value>
        </init-param>-->


    </servlet>

    <servlet-mapping>
        <servlet-name>kaptcha</servlet-name>
        <url-pattern>/kaptcha.jpg</url-pattern>
    </servlet-mapping>

推荐阅读更多精彩内容