9.3 图片验证码技术
学习目标
熟悉实现验证码功能的操作步骤。
为了防止用户利用程序、机器人的方式自动注册、登录、灌水、发布信息,大多数网站都采用了验证码技术。所谓验证码,就是将一串随机产生的数字或符号,生成一幅图片,图片里加上一些干扰元素,例如随机画数条直线,画一些点等,由用户肉眼识别其中的验证码信息,输入表单提交网站验证,验证成功后才能使用某项功能。
在ASP.NET环境中,要通过程序的方式在用户登录页面增加验证码功能,可以归纳为如下几个步骤。
(1)随机产生字符串。
(2)把随机生成的字符串转换成图片输出。
(3)使用Session记录随机字符串。
(4)登录页面引用验证码及程序实现。
9.3.1 随机产生字符串
在ASP.NET中,程序产生随机字符串核心使用的就是Random类来完成的。比如下面的代码返回的就是[0,3)之间的一个随机整数,即0、1、2中的某个整数。
Random rand = new Random(); Response.Write(rand.Next(3));
而接下来的代码就返回的是[5,8)之间的一个随机整数,即5、6、7中的某个整数。
Random rand = new Random(); Response.Write(rand.Next(5,8));
其实这里讲的就是Random这个类的Next方法的两个重载形式,当然还有其他形式,不过常用的就是这两个形式。
而验证码这里需要的是由字母、数字组成的字符串,而非纯粹的数字构造的字符串,这就用到了C#数组的功能,不妨定义如下字符串:
string str = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz1234567890";
其实定义的字符串变量str在C#语言中也就是一个字符数组,于是可以通过如下代码遍历该字符数组中的每个成员。
string str = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjklmnopqrstuvwxyz1234567890"; for (int i = 0; i < str.Length; i++) { Response.Write(str[i].ToString ()+"<br />"); }
显然这里用到的字符数组str的索引号本身就是整数,取值范围为0~str.length-1,这样一来通过Random类来获取0~str.length-1范围之间的一个随机数,然后再作为字符数组str的索引号,得到的当然就是一个随机字符了,而要得到多个随机字符构成随机字符串,那就多循环几次即可。接下来就给出完整的生成随机字符串的方法代码。
//获取一个随机字符串 public static string GetRandom(int n) { string str = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjklmnopqrstu vwxyz1234567890"; Random random = new Random(); string result = string.Empty; for (int i = 1; i <= n; i++) { result += str[random.Next(0, str.Length - 1)].ToString(); } return result; }
通常都是把这个方法放在一个通用功能类Common.cs中,这个需要自己创建,创建也很简单,就是在网站项目中右击,在弹出的快捷菜单中依次选择“添加”|“类”,然后命名为Common即可。
9.3.2 把随机生成的字符串转换成图片输出
要把随机生成的字符串转换成图片输出,这就用到了C#语言GDI+(Graphics Device Interface,图形设备接口)编程。
在ASP.NET中,通常GDI+编程用到下面的这些类。
System.Random System.Drawing.Bitmap System.Drawing.SolidBrush System.Drawing.PointF System.IO.MemoryStream
接下来给出完整的字符串转换图片输出代码。
//输出图片 public static void DisplayImage(string str) { //创建一个图片区域设置宽度和高度 Bitmap image = new Bitmap(50, 20); //获取这个图片区域,便于在其中添加元素 Graphics g = Graphics.FromImage(image); //设置图片背景为白色 g.Clear(Color.White); //画线构造一些盲点,意思就是故意让图片显示不清楚些 Random random = new Random(); for (int i = 0; i < 10; i++) { int x1 = random.Next(image.Width); int y1 = random.Next(image.Height); int x2 = random.Next(image.Width); int y2 = random.Next(image.Height); g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2); } //定义字体对象,设置字体和字体大小 Font font = new Font("黑体", 12); //定义画笔颜色 SolidBrush brush = new SolidBrush(Color.BlueViolet); //定义画笔写信息的位置坐标点,左上角为(0,0)点 PointF point = new PointF(2, 2); //写字符串到图片中 g.DrawString(str, font, brush, point); //给图片画边框 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); //生成一个内存流对象,便于存储并输出图片 MemoryStream ms = new MemoryStream(); //保存绘制的图片到内存流ms中 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); //输出二进制图片流 HttpContext.Current.Response.BinaryWrite(ms.ToArray()); }
通常都是把这个方法放在一个通用功能类Common.cs中。然后创建一个新的网页CheckCode.aspx,在其程序页面中输入如下代码。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; public partial class CheckCode : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Common.DisplayImage(Common.GetRandom(5)); } }
然后运行CheckCode.aspx,显示效果如图9-11所示,而且页面每刷新一次,图片中的字符串变化一次,这就是使用Common.GetRandom(5)带来的效果。
9.3.3 使用Session记录随机字符串
由于程序在引用上述CheckCode.aspx后,用户在页面上可以看到验证码这个随机字符串,而用户输入后,程序要判断,所以系统每产生一次随机字符串,就需要保存到一个变量中,这里当然用的就是Web的通用方法:Session变量来完成。
通过在DisplayImage方法中的首行增加如下代码:
HttpContext.Current.Session["code"] = str;
这样一来,页面在引用验证码输入后,就可以使用Session["code"]来判断用户输入的验证码是否正确,进而给出相应的提示信息。
9.3.4 登录页面引用验证码及程序实现
登录、注册、发布帖子、写博客、写微博等都会用到验证码效果,这里就以用户登录为例来说明如何引用验证码。
(1)首先需要做一个完整的登录表单页面Login.aspx,如图9-12所示。
接下来给出登录表单页面Login.aspx的完整脚本源代码。
<%@ Page Language="C#" %> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>用户登录</title> <link href="css/style.css" rel="stylesheet" /> <script type="text/javascript" src="Scripts/jquery-1.7.1.js"></script> </head> <script type="text/javascript"> //表单验证代码 function check() { var username = $("#username").val(); var pwd = $("#pwd").val(); var code = $("#code").val(); if (username == "") { alert("用户名不能为空!"); $("#username").focus(); return; } if (pwd == "") { alert("登录口令不能为空!"); $("#pwd").focus(); return; } if (code == "") { alert("验证码不能为空!"); $("#code").focus(); return; } form1.submit(); } </script> <body> <form action="dologin.aspx" method="post" id="form1" name="form1"> <table width="415" border="0" cellspacing="0" cellpadding="0" style="border: 1px solid #333; margin: 10px;"> <tr> <td height="30" colspan="3" align="center"> <strong>用户登录</strong></td> </tr> <tr> <td width="89" height="30" align="center">用户名称</td> <td height="30" colspan="2"> <input type="text" id="username" name="username" class="txt" /></td> </tr> <tr> <td height="30" align="center">登录口令</td> <td height="30" colspan="2"> <input type="password" id="pwd" name="pwd" class="txt" /></td> </tr> <tr> <td height="30" align="center">输入验证码</td> <td width="131" height="30"> <input type="text" id="code" name="code" class="txt" style= "width: 80px;" /></td> <td width="193"> </td> </tr> <tr> <td height="30" align="center"> </td> <td height="30" colspan="2"> <input type="button" name="button" id="button" value="登录" onclick="check()" style="width: 80px; height: 30px;" /> <input type="button" name="button" id="button1" value="重置" onclick="form1.reset()"style="width: 80px; height: 30px;" /> </td> </tr> </table> </form> </body> </html>
(2)其次,需要在mydb数据库中创建一个用户表tb_user,表结构如图9-13所示。
创建tb_user的完整数据库脚本如下。
create table tb_user ( id int identity, username varchar(50) primary key, pwd varchar(50), dt datetime default getdate() ) go insert into tb_user(username,pwd)values('admin','21232F297A57A5 A743894A0E4A801FC3'); go select * from tb_user; go
其中,给表tb _user添加的模拟记录,用户名称为admin,而口令也正是使用前面讲的Get_MD5方法处理字符串admin后的结果,也就是添加的模拟账号/口令为admin/admin。
(3)打开登录页面Login.aspx,给验证码一行的最后一列增加验证码引用代码,下面给出这行的完整代码。
… <tr> <td height="30" align="center">输入验证码</td> <td width="131" height="30"> <input type="text" id="code" name="code" class="txt" style= "width: 80px;" /></td> <td width="193"> <img src="CheckCode.aspx" style="cursor:pointer;" title="看不清,单击换一张" onclick="this.src='CheckCode.aspx?random='+Math.random()" /> <span style=" color:#ff0000; font-size:12px;">验证码不区分大小写</span> </td> </tr> …
再次运行页面Login.aspx,显示效果如图9-14所示。
(4)显然,验证码已经引入到登录页面中了,而且也支持“单击换一张”的功能,接下来就是完成dologin.aspx页面,把登录的处理程序页面代码完成。dologin.aspx完整处理代码如下。
protected void Page_Load(object sender, EventArgs e) { string username = Request.Form["username"]; string pwd = Request.Form["pwd"]; pwd = Common.Get_MD5(pwd); string code = Request.Form["code"].ToLower(); DBHelper db = new DBHelper(); //首先判断验证码是否正确 if (code == Session["code"].ToString().ToLower()) { //接下来检查用户名是否正确 Hashtable ht = new Hashtable(); ht.Add("@username", username); string sql="select username,pwd from tb_user where username=@username"; DataRow row = db.GetRow(sql, ht); if (row == null) { Response.Write("<script>alert('用户名输入错误!'); history.back();</script>"); } else { //接下来检查口令输入是否正确 if (pwd == row["pwd"].ToString()) { Response.Write("成功登录了!"); } else { Response.Write("<script>alert('口令输入错误!'); history.back();</script>"); } } } else { Response.Write("<script>alert('验证码输入错误!'); history.back();</script>"); } }