当前位置: 代码迷 >> Web前端 >> 神奇的验证码,小弟我们一起来探究
  详细解决方案

神奇的验证码,小弟我们一起来探究

热度:420   发布时间:2012-09-07 10:38:15.0
神奇的验证码,我们一起来探究

一开始接触验证码,觉得很麻烦,每次登陆还得输入验证码,甚是麻烦,不是说过一切为了人民服务吗?为什么不给用户带来方便,我想没有哪位用户是愿意输入验证码的,特别是对于视力不是太好的朋友们,有时候很变态,明明记得是输入对了,但是最后显示的是验证码输入错误,这是否跟咱们的软件为人民服务冲突呢?最为难的是春运的时候买火车票,很多人都是买到票之后,由于验证码输入错误或者提交失败一直到提交成功的时候,显示的却是票不足,购票不成功,这多伤人啊。为此,我百思不得其解。直到那一天……

我恍然发现,咱们想的都是太自私了,都是为自己的方便着想,没有想到程序员的难处,当然程序员是想让大家方便,但是没有事事都是两全其美的,所以为了大家方便的同时,程序员遇到更大的一个难题就是安全,而验证码是一种区分用户是计算机和人的全自动程序,可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试,实际上是用验证码是现在很多网站通行的方式(比如招商银行的网上个人银行,百度社区),程序员利用比较简易的方式实现了这个功能。到现在也许有很多的用户反映到登陆输入验证码太麻烦了,所以看到有一些网站是第一次登陆不需要输入验证码,然后你输入用户名或者密码不对的时候,就是登陆不成功的时候,验证码才出来,毕竟那是电脑,没有人脑那么灵活,所以程序员就让给电脑一个灵感,但登陆不成功的时候,为了安全,就给出验证码,这就很好的有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试了,这就实现了安全。我们看一下网易163邮箱是怎么做的:

当你多次登陆不成功的时候,他会弹出这么一个窗口:

我们的验证码出来了,这就证明了咱们的验证码确实能有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试进行破解。所以当我们要输入验证码的时候,我们要想到这些,就不必心烦了,程序员想的不是不周到,是想让大家的数据更加安全,也为我们的秘密泄露加上了很好的保护套。

至于春运买火车票的,我看见很多人抱怨说输入验证码导致自己没买到票,这么说吧,要是不输入验证码,那更买不到了,至于人家的铁道部网站做的怎么样,咱们先不讨论。

相信很多人跟我一样,有过这样的背景,其实挺好,没有这样的疑惑,我们就没有进步的动力,就难以跟上时代的步伐。我写这篇博客就是想给大家解开这个迷惑,到底这是怎么做到的呢?

我在想,为什么验证码那么神奇呢?为什么验证码能区分用户是计算机和人呢?他是怎么做到的呢?原因很简单,我们来看看内部的代码运行情况:

我们做一个登陆页面,来看看验证码的背后是怎么工作的:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login_blog.aspx.cs" Inherits="WebApplication1.Login_blog" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>登陆验证码测试</title>
    <link href="css/Login_blog.css" rel="stylesheet" type="text/css" />
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <h3>
            登录验证码测试
        </h3>
        <div id="Login_blog">
            <p>
                用户名:
                <asp:TextBox ID="txtUserName" runat="server" CssClass="textbox"></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server" ErrorMessage="请输入用户名"
                    Text="*" ControlToValidate="txtUserName"></asp:RequiredFieldValidator>
            </p>
            <p>
                密码:
                <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" CssClass="textbox"></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator2" runat="server" ErrorMessage="请输入密码!"
                    Text="*" ControlToValidate="txtPassword"></asp:RequiredFieldValidator>
            </p>
            <p>
                验证码:<img src="../handler/WaterMark.ashx" id="vimg" alt="" onclick="changeCode()" />
                <asp:TextBox ID="txtCode" runat="server" CssClass="txtCode"></asp:TextBox>
                <asp:RequiredFieldValidator ID="RequiredFieldValidator3" runat="server" ErrorMessage="请输入验证码!"
                    Text="*" ControlToValidate="txtCode"></asp:RequiredFieldValidator>
            </p>
            <p>
                <asp:Button ID="btnLogin" runat="server" Text="登录" OnClick="btnLogin_Click" />
            </p>
            <asp:ValidationSummary ID="ValidationSummary1" runat="server" ShowMessageBox="true"
                ShowSummary="false" />
        </div>
    </div>
    </form>
</body>
</html>
在Login_blog.aspx.cs里边这样
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BLL;
using System.Web.Security;

namespace WebApplication1
{
    public partial class Login_blog : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }
        //登录按钮
        protected void btnLogin_Click(object sender, EventArgs e)
        {
            //判断验证码是否输入正确
            string code = txtCode.Text.Trim();
            string rightCode = Session["Code"].ToString();
            if (code!=rightCode)
            {
                Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('验证码输入错误!');</script>");
                return;
            }
            string name = txtUserName.Text.Trim();
            string pwd = txtPassword.Text.Trim();

            bool b = LoginManager.Login(name, pwd);
            if (b)
            {
                //登录成功
                Session["admin"] = name;
                Response.Redirect("http://blog.csdn.net/yi_zz");
            }
            else
            {
                    //登录失败
                Page.ClientScript.RegisterStartupScript(Page.GetType(), "message", "<script language='javascript' defer>alert('登陆失败,用户名或者密码错误!');</script>");
            }
        }
        

    }
}
css文件中我们排一下版:

/*
*登陆验证码测试
*/

     *
{
    margin :0;
    padding :0;
    }
body 
{
    font-size :14px;
}


#loginfrm #login p
{
    padding-bottom :10px;
    }
.textbox
{
    width :150px;
    }
.txtCode
{
    width :73px;
    }
我们在逻辑层写一点判断:
/*
 * 创建人:宗毅   
 * 创建时间:2012年8月11日19:38:27
 * 说明:登陆的业务逻辑类
 * 版权所有:
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BLL
{
   public  class LoginManager
   {
       #region 用户登陆是否成功
       /// <summary>
       /// 用户登陆是否成功
       /// </summary>
       /// <param name="name">用户名</param>
       /// <param name="pwd">密码</param>
       /// <returns></returns>
       public static bool Login(string name, string pwd)
       {
           bool flag = false;
           if ("zongyi" == name && "czy" == pwd)
           {
               flag = true;
           }
           return flag;
       }
       #endregion
   }
    
}
最后看看验证码这部分的代码:
/*
 * 验证码
*/
using System;
using System.Web;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Web.SessionState;

public class WaterMark : IHttpHandler, IRequiresSessionState  // 要使用session必须实现该接口,记得要导入System.Web.SessionState命名空间
{

    public void ProcessRequest(HttpContext context)
    {
        string checkCode = GenCode(5);  // 产生5位随机字符
        context.Session["Code"] = checkCode; //将字符串保存到Session中,以便需要时进行验证
        System.Drawing.Bitmap image = new System.Drawing.Bitmap(70, 22);
        Graphics g = Graphics.FromImage(image);
        try
        {
            //生成随机生成器
            Random random = new Random();

            //清空图片背景色
            g.Clear(Color.White);

            // 画图片的背景噪音线
            int i;
            for (i = 0; i < 25; i++)
            {
                int x1 = random.Next(image.Width);
                int x2 = random.Next(image.Width);
                int y1 = random.Next(image.Height);
                int y2 = random.Next(image.Height);
                g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2);
            }

            Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold));
            System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2F, true);
            g.DrawString(checkCode, font, brush, 2, 2);

            //画图片的前景噪音点
            g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1);
            System.IO.MemoryStream ms = new System.IO.MemoryStream();
            image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif);
            context.Response.ClearContent();
            context.Response.ContentType = "image/Gif";
            context.Response.BinaryWrite(ms.ToArray());
        }
        finally
        {
            g.Dispose();
            image.Dispose();
        }
    }

    /// <summary>
    /// 产生随机字符串
    /// </summary>
    /// <param name="num">随机出几个字符</param>
    /// <returns>随机出的字符串</returns>
    private string GenCode(int num)
    {
        string str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        char[] chastr = str.ToCharArray();
        string code = "";
        Random rd = new Random();
        int i;
        for (i = 0; i < num; i++)
        {
            //code += source[rd.Next(0, source.Length)];
            code += str.Substring(rd.Next(0, str.Length), 1);
        }
        return code;

    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

}

我们看到这个界面:

这个登录验证码的窗体已经算完成了,我们看的出来验证码其实就是通过一张图片,来区分用户和计算机,就能达到很好的防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登陆尝试;所以为了咱们的网络安全,这个验证码确实发挥了很大的作用。



 

 

34楼aboy1232小时前
列害啊
Re: yi_zz1小时前
回复aboy123n一起学习,共同进步。
33楼ddrsun13小时前
我运行你的WaterMark.ashxn提示 无法显示 XML 页。 n无法查看使用 样式表的 XML 输入。请更正错误然后单击 刷新 按钮,或稍后重试。 nn什么原因,需要注意些什么吗?
Re: yi_zz11小时前
回复ddrsunn稍后我给你发邮件。
32楼lfmilaoshi昨天 19:13
俨然高手。。。米老师
Re: yi_zz昨天 19:32
回复lfmilaoshin谢谢米老师
31楼liujiahan629629昨天 17:01
首页,厉害!
Re: yi_zz昨天 19:01
回复liujiahan629629n谢谢。
30楼cxz_azhong4812昨天 12:40
很好的分享!
Re: yi_zz昨天 12:41
回复cxz_azhong4812n谢谢~
29楼L_Lycos昨天 10:12
[code=java]nstring str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; n[/code]n中的1 、I 、L、0、O去掉,这样子用户看的时候就不怎么容易输入错误。自己笑笑看法、嘎嘎~~~
Re: xiaoxian8023昨天 11:43
回复L_Lycosn这个主意很好,非常人性化
Re: yi_zz昨天 11:56
回复L_Lycosn非常好~
28楼sandeziVIP昨天 10:11
现在的验证码真是越来越牛逼,昨天在苏宁易购上抢东西的时候竟然有这样一个验证码:戒指一般是戴在大腿上吗?让人无语···
Re: yi_zz昨天 10:12
回复sandeziVIPn这样也行?呵呵
27楼lishuangzhe7047前天 21:26
是安全。n但是有些东西还是看不懂啊。
Re: yi_zz昨天 22:01
回复lishuangzhe7047n看不懂再看一遍嘛
26楼lishehe前天 20:16
火了
Re: yi_zz前天 20:22
回复lishehen常来看看
25楼ddrsun前天 19:52
你好,我的刷新验证码时遇到问题n可以刷新,但是还是第二次点击之后的,都是只变形状和颜色,字符没变n还有,我断点到CreatValitedateString(6)生成验证字符串,load的时候他运行了两次,才显示页面,验证图片上出现的是第一次生成的字符串n我删了function Change()之后,CreatValitedateString(6)生成验证字符串就只运行一次正常了,但就不能刷新了啊?怎么办nn<script>代码:nfunction Change() {nn var a = "<%=CreatValitedateString(6)%>";n n document.getElementById("ValidateImage").src = "ValidateImage.aspx?Validator=" + a;n}nimage代码:n<asp:Image ID="ValidateImage" runat="server" Height="30px" n style="margin-left: 39px;cursor:pointer" Width="125px" ImageAlign="AbsBottom" onclick="javascript:Change();" />nn这问题很久都没有解决啊,要不要发我的文件给你帮看看可以吗?
Re: yi_zz前天 20:00
回复ddrsunn好,发我邮箱。
24楼StubbornPotatoes前天 16:40
推荐了。
Re: yi_zz前天 19:14
回复StubbornPotatoesnO(∩_∩)O~
23楼dandanzmc前天 16:36
向毅哥学习!!!
Re: yi_zz前天 16:36
回复dandanzmcn一起加油,一起进步
22楼jeep0521前天 15:15
分析的很有道理 不过我菜鸟一个 代码还有很多不懂得地方 呵呵
Re: yi_zz前天 15:41
回复jeep0521n都一样,一起学习
21楼liushuijinger前天 13:25
可以看到一条评论,什么情况?
Re: yi_zz前天 14:38
回复liushuijingern很神奇,很神奇
20楼ssrc0604hx前天 13:19
第一次登录不需要验证码,如果登录不成功,则需要写验证码,这个是很好的用户体验。nn可以通过在session中保存一个标识判断是否有过登录不成功的“前科”。n如果安全级别更高一些,则需要将客户端的ip等信息保存并判断。这样就算重开一个session也可以被检测出来。
Re: yi_zz前天 13:20
回复ssrc0604hxn大牛啊~
19楼liushuiwuyizhe前天 13:19
先记下,很有用
Re: yi_zz前天 13:19
回复liushuiwuyizhen谢谢~
18楼hubeihq前天 13:18
路过,分析的有道理
Re: yi_zz前天 13:18
回复hubeihqn谢谢
17楼zhuankeshumo前天 13:15
貌似自己做的验证码长的比较丑 达不到专业水平
Re: yi_zz前天 13:15
回复zhuankeshumon慢慢来,都一样。
Re: zhuankeshumo前天 13:16
//画图片的前景噪音点 n g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); 这是边框 你噪音点忘了回复yi_zz
Re: llhhyy1989前天 13:16
回复zhuankeshumon专业术语,不懂,求指教!
Re: zhuankeshumo前天 13:16
回复llhhyy1989http://topic.csdn.net/u/20120723/16/e700e403-a7b2-4d91-a775-ad380e5dd28a.html
Re: yi_zz前天 13:16
[code=csharp]n//画图片的前景噪音点n for (int i = 0; i < 100; i++)n {n Point tem_point = new Point(random.Next(image.Width), random.Next(image.Height));n image.SetPixel(tem_point.X, tem_point.Y, Color.FromArgb(random.Next()));n }n[/code]
16楼edcdc009前天 13:14
好东西,知识就在身边!学习了!!
Re: yi_zz前天 13:15
回复edcdc009n谢谢,常来看看
15楼yinjingjing198808前天 13:14
验证码的强大!
Re: yi_zz前天 13:14
回复yinjingjing198808n呵呵,常来看看哈
14楼xiaoxian8023前天 09:49
学习了
Re: yi_zz前天 09:55
回复xiaoxian8023n一起学习
13楼lfsfxy9前天 00:11
恩,文章写得很好啊,题材是旧的,但是文章和引导主线是很新的。nn加油。
Re: yi_zz前天 08:22
回复lfsfxy9n这话说的好哇。
12楼gwblue前天 23:14
提前学习了!
Re: yi_zz前天 23:34
回复gwbluen常来看看,将感激不尽~
11楼hyq017970前天 23:07
受教了...
Re: yi_zz前天 23:07
回复hyq017970n共同学习~
10楼jiuqiyuliang前天 22:27
好文章啊,陈总,回头一定好好研究!!!现在能力有限啊
Re: yi_zz前天 22:45
回复jiuqiyuliangn不急,慢慢来
9楼liyepeng0323天前 20:53
可以啊。
Re: yi_zz3天前 21:32
回复liyepeng032n呵呵
8楼tiancaixuchao20093天前 19:51
作者很认真,每条评论都回复,学习啦
Re: yi_zz3天前 19:54
回复tiancaixuchao2009n谢谢啦~
7楼lyg6737707123天前 17:16
留着以后用。。。李亚光
Re: yi_zz3天前 19:23
回复lyg673770712n恩,以后就会用到。
6楼ddrsun3天前 16:41
嗯嗯,谢谢你啊,都解决了nonclick:“javascript:Changcode()“可以这样写nfunction Changcode()n{ndocument.getElementById("ValidateImage").src = "Hendler/ValidateImage.ashx?f=" + Math.random();//记得加random()函数使之前的无效就可以刷新ashx了n}
Re: yi_zz3天前 17:08
回复ddrsunn客气了~
5楼ddrsun3天前 12:33
对了,刷新验证码里<img src="../handler/WaterMark.ashx" id="vimg" alt="" onclick="changeCode()" /> 的nchangeCode()函数呢?我想看看是怎么做的
Re: yi_zz3天前 14:25
回复ddrsunn稍后我给你发邮件
4楼hz_gis3天前 11:13
=.= 我以为是破解的呢...
Re: yi_zz3天前 11:21
回复hz_gisn呵呵, 是底层的一些东西。
3楼xqf3093天前 10:24
[e01]
Re: yi_zz3天前 10:43
回复xqf309n这个表情我这边显示不出来了哦,呵呵
2楼hekuerle3天前 10:10
图片出不来怎么办啊?n<img src="../handler/WaterMark.ashx" id="vimg" alt="" onclick="changeCode()" />n其中的changeCode()没有实现
Re: yi_zz3天前 10:11
回复hekuerlen是路径不对吗?
1楼lbq6136133天前 08:13
顶啦!
Re: yi_zz3天前 09:40
回复lbq613613n3Q
  相关解决方案