首页 > 技术文章 > JAVAWEB项目实现验证码中文、英文、数字组合

wkrbky 2017-01-02 23:08 原文

验证码基础

一.什么是验证码及它的作用

   验证码为全自动区分计算机和人类的图灵测试的缩写,是一种区分用户是计算机的公共全自动程序,这个问题可以由计算机生成并评判,但是必须只有人类才能解答.可以防止恶意破解密码、刷票、论坛灌水、有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录。

 

二.图文验证码的原理

   在servlet中随机生成一个指定位置的验证码,一般为四位,然后把该验证码保存到session中.在通过Java的绘图类以图片的形式输出该验证码。为了增加验证码的安全级别,可以输出图片的同时输出干扰线,最后在用户提交数据的时候,在服务器端将用户提交的验证码和Session保存的验证码进行比较。

 

三.验证码所需的技术

    :i.因为验证码中的文字,数字,应为都是可变的,故要用到随机生成数技术。

       ii.如果验证码中包含汉字,则要用到汉字生成技术.

       iii.可以使用Ajax技术实现局部刷新

       iv.可以使用图片的缩放和旋转技术,

       vi.随机绘制干扰线(可以是折现,直线等)

       vii.如果考虑到验证码的安全性,可以使用MD5加密.

中文、英文、数字组合验证码:

package randCodeImage.servlet;

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.io.*;
import java.util.*;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.imageio.ImageIO;
/**
 * 随机生成英文、数字、中文验证图片
 * 
 * @author wkr
 * 
 */
public class PictureCheckCode extends HttpServlet {

    private static final long serialVersionUID = 1L;

    public PictureCheckCode() {
        super();
    }

    public void destroy() {
        super.destroy(); 
    }

    public void init() throws ServletException {
        super.init();
    }
    /*该方法主要作用是获得随机生成的颜色*/ 
    public Color getRandColor(int s,int e){
        Random random=new Random ();
        if(s>255) s=255;
        if(e>255) e=255;
        int r,g,b;
        r=s+random.nextInt(e-s);    //随机生成RGB颜色中的r值
        g=s+random.nextInt(e-s);    //随机生成RGB颜色中的g值
        b=s+random.nextInt(e-s);    //随机生成RGB颜色中的b值
        return new Color(r,g,b);
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //设置不缓存图片
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "No-cache");
        response.setDateHeader("Expires", 0);
//        request.setCharacterEncoding("UTF-8");
//        request.setCharacterEncoding("UTF-8");
//        response.setContentType("text/html;charset=UTF-8");
        //指定生成的响应图片,一定不能缺少这句话,否则错误.
        response.setContentType("image/jpeg");
        int width=86,height=32;        //指定生成验证码的宽度和高度
        BufferedImage image=new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);    //创建BufferedImage对象,其作用相当于一图片
        Graphics g=image.getGraphics();        //创建Graphics对象,其作用相当于画笔
        Graphics2D g2d=(Graphics2D)g;        //创建Grapchics2D对象
        Random random=new Random();
        Font mfont=new Font("楷体",Font.BOLD,24);    //定义字体样式
        g.setColor(getRandColor(200,250));
        g.fillRect(0, 0, width, height);    //绘制背景
        g.setFont(mfont);                    //设置字体
        g.setColor(getRandColor(180,200));
        
        //绘制100条颜色和位置全部为随机产生的线条,该线条为2f
        for(int i=0;i<100;i++){
            int x=random.nextInt(width-1);
            int y=random.nextInt(height-1);
            int x1=random.nextInt(6)+1;
            int y1=random.nextInt(12)+1;
            BasicStroke bs=new BasicStroke(2f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_BEVEL);    //定制线条样式
            Line2D line=new Line2D.Double(x,y,x+x1,y+y1);
            g2d.setStroke(bs);
            g2d.draw(line);        //绘制直线
        }
        //输出由英文,数字,和中文随机组成的验证文字,具体的组合方式根据生成随机数确定。
        String sRand="";
        String ctmp="";
        int itmp=0;
        //制定输出的验证码为四位
        for(int i=0;i<4;i++){
            switch(random.nextInt(3)){
                case 1:        //生成A-Z的字母
                     itmp=random.nextInt(26)+65;
                     ctmp=String.valueOf((char)itmp);
                     break;
                case 2:        //生成汉字
                     String[] rBase={"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"}; 
                     //生成第一位区码
                     int r1=random.nextInt(3)+11;
                     String str_r1=rBase[r1];
                     //生成第二位区码
                     int r2;
                     if(r1==13){
                         r2=random.nextInt(7);     
                     }else{
                         r2=random.nextInt(16);
                     }
                     String str_r2=rBase[r2];
                     //生成第一位位码
                     int r3=random.nextInt(6)+10;
                     String str_r3=rBase[r3];
                     //生成第二位位码
                     int r4;
                     if(r3==10){
                         r4=random.nextInt(15)+1;
                     }else if(r3==15){
                         r4=random.nextInt(15);
                     }else{
                         r4=random.nextInt(16);
                     }
                     String str_r4=rBase[r4];
                     //将生成的机内码转换为汉字
                     byte[] bytes=new byte[2];
                     //将生成的区码保存到字节数组的第一个元素中
                     String str_12=str_r1+str_r2;
                     int tempLow=Integer.parseInt(str_12, 16);
                     bytes[0]=(byte) tempLow;
                     //将生成的位码保存到字节数组的第二个元素中
                     String str_34=str_r3+str_r4;
                     int tempHigh=Integer.parseInt(str_34, 16);
                     bytes[1]=(byte)tempHigh;
                     ctmp=new String(bytes,"GB2312");
                     break;
                default:
                     itmp=random.nextInt(10)+48;
                     ctmp=String.valueOf((char)itmp);
                     break;
            }
            sRand+=ctmp;
            Color color=new Color(20+random.nextInt(110),20+random.nextInt(110),random.nextInt(110));
            g.setColor(color);
            //将生成的随机数进行随机缩放并旋转制定角度 PS.建议不要对文字进行缩放与旋转,因为这样图片可能不正常显示
            /*将文字旋转制定角度*/
            Graphics2D g2d_word=(Graphics2D)g;
            AffineTransform trans=new AffineTransform();
            trans.rotate((45)*3.14/180,15*i+8,7);
            /*缩放文字*/
            float scaleSize=random.nextFloat()+0.8f;
            if(scaleSize>1f) scaleSize=1f;
            trans.scale(scaleSize, scaleSize);
            g2d_word.setTransform(trans);
            g.drawString(ctmp, 15*i+18, 14);
        }
        System.out.println("验证码:>>>"+sRand);
        HttpSession session=request.getSession(true);
        session.setAttribute("randCheckCode", sRand);
        g.dispose();    //释放g所占用的系统资源
        ImageIO.write(image,"JPEG",response.getOutputStream());    //输出图片
    }
}

英文、数字组合验证码(附加读取配置文件):

package randCodeImage.servlet;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import randCodeImage.util.ResourceUtil;

//import com.sun.image.codec.jpeg.JPEGCodec;
//import com.sun.image.codec.jpeg.JPEGEncodeParam;
//import com.sun.image.codec.jpeg.JPEGImageEncoder;

/**
 * 随机生成英文、数字验证图片
 * 
 * @author wkr
 * 
 */
public class RandCodeImageServlet extends HttpServlet {
    private static final long serialVersionUID = -1257947018545327308L;
    private static final String SESSION_KEY_OF_RAND_CODE = "randCode"; // todo 要统一常量
    /**
     * 
     */
    private static final int count = 200;

    /**
     * 定义图形大小
     */
    private static final int width = 105;
    /**
     * 定义图形大小
     */
    private static final int height = 35;
    // private Font mFont = new Font("Arial Black", Font.PLAIN, 15); //设置字体
    /**
     * 干扰线的长度=1.414*lineWidth
     */
    private static final int lineWidth = 2;

    @Override
    public void doGet(final HttpServletRequest request,
            final HttpServletResponse response) throws ServletException,
            IOException {
        // 设置页面不缓存
        response.setHeader("Pragma", "No-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        // response.setContentType("image/png");

        // 在内存中创建图象
        final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 获取图形上下文
        final Graphics2D graphics = (Graphics2D) image.getGraphics();

        // 设定背景颜色
        graphics.setColor(Color.WHITE); // ---1
        graphics.fillRect(0, 0, width, height);
        // 设定边框颜色
//        graphics.setColor(getRandColor(100, 200)); // ---2
        graphics.drawRect(0, 0, width - 1, height - 1);

        final Random random = new Random();
        // 随机产生干扰线,使图象中的认证码不易被其它程序探测到
        for (int i = 0; i < count; i++) {
            graphics.setColor(getRandColor(150, 200)); // ---3

            final int x = random.nextInt(width - lineWidth - 1) + 1; // 保证画在边框之内
            final int y = random.nextInt(height - lineWidth - 1) + 1;
            final int xl = random.nextInt(lineWidth);
            final int yl = random.nextInt(lineWidth);
            graphics.drawLine(x, y, x + xl, y + yl);
        }

        // 取随机产生的认证码(4位数字)
        final String resultCode = exctractRandCode();
        for (int i = 0; i < resultCode.length(); i++) {
            // 将认证码显示到图象中,调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
            // graphics.setColor(new Color(20 + random.nextInt(130), 20 + random
            // .nextInt(130), 20 + random.nextInt(130)));

            // 设置字体颜色
            graphics.setColor(Color.BLACK);
            // 设置字体样式
//            graphics.setFont(new Font("Arial Black", Font.ITALIC, 18));
            graphics.setFont(new Font("Times New Roman", Font.BOLD, 24));
            // 设置字符,字符间距,上边距
            graphics.drawString(String.valueOf(resultCode.charAt(i)), (23 * i) + 8, 26);
        }

        // 将认证码存入SESSION
        request.getSession().setAttribute(SESSION_KEY_OF_RAND_CODE, resultCode);
        // 图象生效
        graphics.dispose();

        // 输出图象到页面
        ImageIO.write(image, "JPEG", response.getOutputStream());
    }

    @Override
    public void doPost(final HttpServletRequest request,
            final HttpServletResponse response) throws ServletException,
            IOException {
        doGet(request, response);
    }

    /**
     * @return 随机码
     */
    private String exctractRandCode() {
        final String randCodeType = ResourceUtil.getRandCodeType();
        int randCodeLength = Integer.parseInt(ResourceUtil.getRandCodeLength());
        if (randCodeType == null) {
            return RandCodeImageEnum.NUMBER_CHAR.generateStr(randCodeLength);
        } else {
            switch (randCodeType.charAt(0)) {
            case '1':
                return RandCodeImageEnum.NUMBER_CHAR.generateStr(randCodeLength);
            case '2':
                return RandCodeImageEnum.LOWER_CHAR.generateStr(randCodeLength);
            case '3':
                return RandCodeImageEnum.UPPER_CHAR.generateStr(randCodeLength);
            case '4':
                return RandCodeImageEnum.LETTER_CHAR.generateStr(randCodeLength);
            case '5':
                return RandCodeImageEnum.ALL_CHAR.generateStr(randCodeLength);

            default:
                return RandCodeImageEnum.NUMBER_CHAR.generateStr(randCodeLength);
            }
        }
    }

    /**
     * 描述:
     * 
     * @param fc
     *            描述:
     * @param bc
     *            描述:
     * 
     * @return 描述:
     */
    private Color getRandColor(int fc, int bc) { // 取得给定范围随机颜色
        final Random random = new Random();
        if (fc > 255) {
            fc = 255;
        }
        if (bc > 255) {
            bc = 255;
        }

        final int r = fc + random.nextInt(bc - fc);
        final int g = fc + random.nextInt(bc - fc);
        final int b = fc + random.nextInt(bc - fc);

        return new Color(r, g, b);
    }
}

/**
 * 验证码辅助类
 * 
 * @author 张国明 guomingzhang2008@gmail.com <br/>
 *         2012-2-28 下午2:15:14
 * 
 */
enum RandCodeImageEnum {
    /**
     * 混合字符串
     */
    ALL_CHAR("123456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"),
    /**
     * 字符
     */
    LETTER_CHAR("abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ"),
    /**
     * 小写字母
     */
    LOWER_CHAR("abcdefghjkmnpqrstuvwxyz"),
    /**
     * 数字
     */
    NUMBER_CHAR("123456789"),
    /**
     * 大写字符
     */
    UPPER_CHAR("ABCDEFGHJKMNPQRSTUVWXYZ");
    /**
     * 待生成的字符串
     */
    private String charStr;

    /**
     * @param charStr
     */
    private RandCodeImageEnum(final String charStr) {
        this.charStr = charStr;
    }

    /**
     * 生产随机验证码
     * 
     * @param codeLength
     *            验证码的长度
     * @return 验证码
     */
    public String generateStr(final int codeLength) {
        final StringBuffer sb = new StringBuffer();
        final Random random = new Random();
        final String sourseStr = getCharStr();

        for (int i = 0; i < codeLength; i++) {
            sb.append(sourseStr.charAt(random.nextInt(sourseStr.length())));
        }

        return sb.toString();
    }

    /**
     * @return the {@link #charStr}
     */
    public String getCharStr() {
        return charStr;
    }

}

读取配置文件工具类:

package randCodeImage.util;

import java.util.ResourceBundle;
//commons-lang-2.6.jar
import org.apache.commons.lang.StringUtils;

/** 
 * 项目参数工具类
 * @author otowa
 *
 */
public class ResourceUtil {

    //获得指定的环境变量的值env若获取不到,由默认值pro代替
    private static final String env = StringUtils.defaultIfBlank(System.getProperty("env"), StringUtils.defaultIfBlank(System.getenv("env"), "pro"));
    
    private static final ResourceBundle sysConfig = java.util.ResourceBundle.getBundle(env+"/sysConfig");
    
    /**
     * 获取随机码的长度
     *
     * @return 随机码的长度
     */
    public static String getRandCodeLength() {
        return sysConfig.getString("randCodeLength");
    }
    /**
     * 获取随机码的类型
     *
     * @return 随机码的类型
     */
    public static String getRandCodeType() {
        return sysConfig.getString("randCodeType");
    }
}

sysConfig.properties配置文件(路径:/resources/dev/sysConfig.properties):

#随机码的长度
randCodeLength=4
#随机码的类型
randCodeType=5

验证码jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
<script type="text/javascript">

    /**
     * 异步刷新验证码
     */
    $('#randCodeImage').click(function(){
        reloadRandCodeImage();
    });
    function reloadRandCodeImage() {
        var date = new Date();
        var img = document.getElementById("randCodeImage");
        img.src='randCodeImageServlet?a=' + date.getTime();
    }
</script>
<script language="javascript">
    /**
     * 异步刷新验证码
     */
    function myReload() {  
        document.getElementById("CreateCheckCode").src = document  
                .getElementById("CreateCheckCode").src  
                + "?nocache=" + new Date().getTime();  
    }  
</script>  
</head>
<body>
    <form action="">
        英文、数字验证码:
        <input type="text" name="code" size="4" >
        <img id="randCodeImage" alt="验证码" src="randCodeImageServlet">
        <a id="verifyChange" href="javascript:void(0);" onclick="reloadRandCodeImage();" >换一张</a>
    </form>
    
    <form action="page/randCodeImage/Check.jsp" method="post">
        中文、英文、数字验证码:
        <input name="checkCode" type="text" id="checkCode" title="验证码区分大小写"  
            size="8" maxlength="4" />  
        <img src="PictureCheckCode" id="CreateCheckCode" align="middle">  
        <a href="javascript:;" onclick="myReload()"> 看不清,换一个</a>  
        <input type="submit" value="提交" />  
    </form>  
</body>
</html>

提交效验jsp页面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ page language="java" import="java.util.*"%> 
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>验证码校验</title>
</head>
<body>
 <%  
         request.setCharacterEncoding("utf-8");
        String checkcode=request.getParameter("checkCode");  
         out.print("<script>alert('提交内容:"+checkcode+"');</script>");  
        if(checkcode.equals("")||checkcode==null){  
            out.print("<script>alert('请输入验证码');window.location.href('index.jsp')</script>");  
        }else{  
            if(!checkcode.equalsIgnoreCase((String)session.getAttribute("randCheckCode"))){  
                out.print("<script>alert('验证码不正确,请重新输入');history.back(-1);</script>");  
            }else{  
                out.print("登录成功");  
            }  
        }  
     %>  
</body>
</html>

web.xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">

  <!-- 随机生成验证码图片 -->
  <servlet>  
    <description>输出验证码</description>  
    <display-name>This is the display name of my J2EE component</display-name>  
    <servlet-name>PictureCheckCode</servlet-name>  
    <servlet-class>randCodeImage.servlet.PictureCheckCode</servlet-class>  
  </servlet>
  
  <servlet>  
    <servlet-name>RandCodeImageServlet</servlet-name>  
    <servlet-class>randCodeImage.servlet.RandCodeImageServlet</servlet-class>  
  </servlet>
  
  <!-- 随机生成验证码图片 -->
  <servlet-mapping>  
    <servlet-name>PictureCheckCode</servlet-name>  
    <url-pattern>/PictureCheckCode</url-pattern>  
  </servlet-mapping> 
<servlet-mapping> <servlet-name>RandCodeImageServlet</servlet-name> <url-pattern>/randCodeImageServlet</url-pattern> </servlet-mapping> <!-- 拦路虎 --> <display-name>sdfsdf</display-name> <welcome-file-list> <!-- 随机生成验证码图片 --> <welcome-file>page/randCodeImage/login.jsp</welcome-file> </welcome-file-list> </web-app>

页面效果:

本文参考:程序员形同陌路

推荐阅读