说说“网站验证码”(原理及源码)

来源:互联网 发布:如何查看淘宝宝贝分类 编辑:程序博客网 时间:2024/05/01 21:15

目前,不少网站为了防止用户利用机器人自动注册、登录、灌水,都采用了验证码技术。所谓验证码,就是将一串随机产生的数字或符号,生成一幅图片, 图片里加上一些干扰象素(防止OCR),由用户肉眼识别其中的验证码信息,输入表单提交网站验证,验证成功后才能使用某项功能。

验证码不同于注册码,注册码是软件作者根据提交的机器码通过特殊算法算出的,能让软件正常运行的密码。而验证码是将一串随机产生的数字或符号,生成一幅图片,图片里加上一些干扰象素(防止OCR),由用户肉眼识别其中的验证码信息,输入表单提交网站验证,验证成功后才能使用某项功能。

验证码作用:防止用户利用机器人自动注册、登录、灌水。

一.常见的验证码
1,四位数字,随机的一数字字符串,最原始的验证码,验证作用几乎为零;

2,CSDN网站用户登录用的是GIF格式,目前常用的随机数字图片验证码。图片上的字符比较中规中矩,验证作用比上一个好。没有基本图形图像学知识的人,不可破!可惜读取它的程序,在CSDN使用它的第一天,好像就在论坛里发布了;

3,QQ网站用户登录用的是PNG格式,图片用的随机数字 随机大写英文字母,每刷新一次,每个字符都会变位置呢!主要是为了防止有人利用软件批量获取QQ号码;

4,MS的hotmail申请时候的是BMP格式, 随机数字 随机大写英文字母 随机干扰像素 随机位置;

5,Google的Gmail注册时候的是JPG格式,随机英文字母 随机颜色 随机位置 随机长度;

6,其他各大论坛的是XBM格式,内容随机。

二.验证码作用分析

验证码起源——

攻击者会使用有害程序注册大量的 Web 服务帐户(如 Passport),并用这些账户进行为其他的用户制造麻烦,如发送垃圾邮件,或通过同时反复登录多个帐户来延缓服务的速度。在大多数情况下,自动注册程序不能识别此图片中的字符。

简单的说呢,验证码就是防止攻击者编写程序,自动注册,重复登录暴力破解密码

验证码实现流程——

服务器端随机生成验证码字符串,写入session中,保存在内存中,并写入图片,发送给浏览器端显示,浏览器端输入验证码图片上字符,然后提交服务器端,提交的字符和服务器端保存的该字符比较是否一致。一致就继续,否则返回提示。

图片的字符识别,就是看图片上的干扰强度了。就实际的效果来说,验证码只是增加攻击者的难度,而不可能完全的防止。

==================================产生彩色验证码==================================

以下代码主要思想来源于:《华夏名网》

文档名:VerifyCode.java ===========================

package com.pannuo.tools; //包名

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

public class VerifyCode {

private String randNum="";

public Color getRandColor(int fc,int bc){//给定范围获得随机颜色
   Random random = new Random();
   if(fc>255) fc=255;
   if(bc>255) bc=255;
   int r=fc+random.nextInt(bc-fc);
   int g=fc+random.nextInt(bc-fc);
   int b=fc+random.nextInt(bc-fc);
   return new Color(r,g,b);
}

public BufferedImage creatImage(){

   // 在内存中创建图象
   int width=60, height=20;
   BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);

   // 获取图像上下文
   Graphics g = image.getGraphics();

   //生成随机类
   Random random = new Random();

   // 设定背景色
   g.setColor(getRandColor(200,250));
   g.fillRect(0, 0, width, height);

   //设定字体
   g.setFont(new Font("Times New Roman",Font.PLAIN,18));


   // 随机产生155条干扰线,使图象中的认证码不易被其他程式探测到
   g.setColor(getRandColor(160,200));
   for (int i=0;i<155;i++) {
    int x = random.nextInt(width);
    int y = random.nextInt(height);
    int xl = random.nextInt(12);
    int yl = random.nextInt(12);
    g.drawLine(x,y,x+xl,y+yl);
   }

   // 取随机产生的认证码(4位数字)
   //String rand = request.getParameter("rand");
   //rand = rand.substring(0,rand.indexOf("."));

   for (int i=0;i<4;i++){
    String rand=String.valueOf(random.nextInt(10)); //得到0~10之间的整数
    randNum+=rand;

    // 将认证码显示到图象中
    g.setColor(new Color(20+random.nextInt(110),20+random.nextInt(110),20+random.nextInt(110)));//调用函数出来的颜色相同,可能是因为种子太接近,所以只能直接生成
    g.drawString(rand,13*i+6,16);
   }

   // 图象生效
   g.dispose();
   return image;
}

public String getRandNum() {
   return randNum;
}
}

文档名:login.jsp(登录页面) ===========================

<BODY>
……
验证码<input type="text" name="verifyCode">
<img src="orderService/user/do/verifyCode" />
……
</BODY>

文档名:userServlet.java(用户的servlet) ===========================

——service(……)方法中

        String pathInfo = request.getPathInfo();
       
        if("/regist".equals(pathInfo)) {          
            registCheck(request,response);          
        } else if("/login".equals(pathInfo)) {
            login(request,response);
        } else if("/findPasswd".equals(pathInfo)) {
            findPasswd(request,response);
        } else if("/alterPasswd".equals(pathInfo)) {
            alterPasswd(request,response);
        } else if("/backHome".equals(pathInfo)) {
            backHome(request,response);
        } else if("/verifyCode".equals(pathInfo)) {
            verifyCode(request,response);
        } else {              
            super.service(request, response);
        }

—— verifyCode(request,response)方法中:

private void verifyCode(HttpServletRequest request, HttpServletResponse response) {

       // TODO Auto-generated method stub
       VerifyCode image = new VerifyCode();
       //       设置页面不缓存
       response.setHeader("Pragma","No-cache");
       response.setHeader("Cache-Control","no-cache");
       response.setDateHeader("Expires", 0);       

        //这句必须写在session之前,否则在验证码验证时,从页面获得值与这里session中的不同!
       BufferedImage buffImage = image.creatImage();


       //       将认证码存入SESSION  
       HttpSession session = request.getSession();
       session.setAttribute("rand",image.getSRand());
       //       输出图象到页面
       try {
           ImageIO.write(buffImage, "JPEG", response.getOutputStream());
       } catch (IOException e) {
           // TODO Auto-generated catch block
           e.printStackTrace();
       }          
   }

——login(request,response)方法中:

private void login(HttpRequest request,HttpResponse response) throws ……{
       request.setCharacterEncoding("UTF-8");
       String userName = request.getParameter("user");
       String password = request.getParameter("passwd");
       String verifyCode = request.getParameter("verifyCode");
      
        if(userName == null || userName == "" ||
                password == null || password == "" ||
                verifyCode == null || verifyCode == "") {
           response.sendRedirect("/dingfan/user/login.jsp");
           return;
       }
      
       String code = (String)(request.getSession().getAttribute("rand"));
      
       if(!code.equals(verifyCode)) {
           request.setAttribute("message", "登录页面:验证码错误");
           RequestDispatcher rd = request.getRequestDispatcher("/user/error.jsp");
           rd.forward(request, response);
           return;
       }

      
       UserEntity user = UserDAO.findByUsername(userName);
      
       if(user == null || user.getLimit().equals(Limit.admin)) {
           request.setAttribute("message", "登录页面:没有此用户");
           RequestDispatcher rd = request.getRequestDispatcher("/user/error.jsp");
           rd.forward(request, response);
           return;
       }
      
       if(user.getPassword().equals(password)) {
           //验证成功则创建一个session
           HttpSession session = request.getSession(true);
           session.setAttribute("user", user);
      
           backHome(request, response);
          
       } else {
           //密码不符
           request.setAttribute("message", "登录页面:密码输入错误");
           RequestDispatcher rd = request.getRequestDispatcher("/user/error.jsp");
           rd.forward(request, response);
       }      
      
   }

以下是一个临时性测试文件,一般jsp中不应该有javaBean和<%……%>。

 

文档名:verifyCode.jsp(对bean的引用) ===========================

<%@ page contentType="image/jpeg" import="javax.imageio.*" %>
<jsp:useBean id="image" scope="session" class="com.pannuo.tools.Image"/>

<%
//配置页面不缓存
response.setHeader("Pragma","No-cache");
response.setHeader("Cache-Control","no-cache");
response.setDateHeader("Expires", 0);


BufferedImage buffImag = image.creatImage();
// 将认证码存入SESSION
session.setAttribute("rand",image.sRand);

// 输出图象到页面
ImageIO.write(buffImg, "JPEG", response.getOutputStream());
%>