asp.net 验证码控件 ASP.net 2.0 开启多个使用图片验证码
2010-10-19 10:31
344 查看
今天在制作网站时发现了关于验证码的一些问题,查遍资料、苦思冥想,耗时一晚才将其拿下,所以在这里做个笔记,也能同大家分享一下经验。
要实现的验证码的主要目标:可以多个页面同时调用而不会产生冲突。
这是用于生成验证码的类,存为CheckCode.cs:
/// <summary>
/// 用于生成验证码及验证图片
/// </summary>
public class CheckCode
{
public CheckCode()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
/// <summary>
/// 生成随机验证码并写入Session
/// </summary>
public static string MakeCode(System.Web.SessionState.HttpSessionState se)
{
string code = "";
string UseTxt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW XYZ123456789";
System.Random r = new Random();
for (int i = 0; i < 4; i++)
{
code += UseTxt[r.Next(UseTxt.Length)];
}
se.Add("CheckCode", code);
return code;
}
/// <summary>
/// 创建验证图片并输出
/// </summary>
public static void CreateImage(string checkCode, System.Web.HttpResponse re)
{
int iwidth = (int)(checkCode.Length * 19);
System.Drawing.Bitmap image = new System.Drawing.Bitmap(iwidth, 25);
Graphics g = Graphics.FromImage(image);
g.Clear(Color.LightGoldenrodYellow);
//定义颜色
Color[] c = { Color.Black, Color.Red, Color.DarkBlue, Color.Blue, Color.DarkGray, Color.Green, Color.OrangeRed, Color.Brown, Color.DarkCyan, Color.Purple };
//定义字体
string[] font = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "Tahoma", "黑体", "幼圆", "宋体" };
Random rand = new Random();
//随机输出噪点
for (int i = 0; i < 50; i++)
{
int x = rand.Next(image.Width);
int y = rand.Next(image.Height);
g.DrawRectangle(new Pen(Color.Orange, 0), x, y, 1, 1);
}
//输出不同字体和颜色的验证码字符
for (int i = 0; i < checkCode.Length; i++)
{
int cindex = rand.Next(c.Length);
int findex = rand.Next(font.Length);
Font f = new System.Drawing.Font(font[findex], 12, System.Drawing.FontStyle.Bold);
Brush b = new System.Drawing.SolidBrush(c[cindex]);
int ii = 2;
if ((i + 1) % 2 == 0)
{
ii = 2;
}
g.DrawString(checkCode.Substring(i, 1), f, b, 3 + (i * 15), ii);
}
//画一个边框
g.DrawRectangle(new Pen(Color.Orange, 0), 0, 0, image.Width - 1, image.Height - 1);
//输出到浏览器
System.IO.MemoryStream ms = new System.IO.MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
re.ClearContent();
re.ContentType = "image/Jpeg";
re.BinaryWrite(ms.ToArray());
g.Dispose();
image.Dispose();
re.End();
}
}
这是绘制为图片供客户端查看的页面CheckImage.aspx,其Load事件内容为:
protected void Page_Load(object sender, EventArgs e)
{
if (Session["CheckCode"] == null) CheckCode.CreateImage("出错了!", this.Response);
else CheckCode.CreateImage(Session["CheckCode"].ToStrin g(), this.Response);
}
这里有个问题,就是我看了很多个网上关于ASP.net验证码的文章,都是让这个图片页自己创建随机的验证码、写入Session然后再输出为图片。
可是这样做有两个弊病,一是倘若用户同时打开多次同一个页面或者打开多个都需要验证码的页面时,不管前几个页面看到的验证图片是什么,都只有输入最后一次打开的页面的验证码才算正确,等于同一时间内多个页面之间只有一个页面的真实的验证码和图片能对应上;
另一个问题是,如果让这个页面控制创建验证码的话,势必每次刷新或提交页面都会被要求输入不同的验证码,如果使用数据验证控件CompareValidator进行验证的话(需要关闭客户端验证,开启的话等于是把验证码明文放在网页源代码里给客户端),我设置的控件属性为:
<asp:CompareValidator
ID="CompareValidator1" runat="server" ControlToValidate="TextBox3" Display="Dynamic"
EnableClientScript="False" ErrorMessage="验证码错误">验证码错误</asp:CompareValidator>
然后在该页面的Load事件中将Session中的"CheckCode"字段读出赋给CompareValidator1的ValueToCompare属性,以给其判断依据。
但这样每次不管你输入的是否正确,都会验证失败,我尝试在提交表单的按钮的Click事件中获取Session["CheckCode"]以及CompareValidator1.ValueToCompare的值,发现每次提交的时候获取的值都是下一次页面刷新后所得的验证码图片中所显示的字符,我想了很多种方法也没解决这个问题,感觉好像是当用户按下提交按钮时服务器要控制页面先去进行各种控件的载入和初始化,然后再去执行提交按钮的事件,所以没办法了,只有控制验证码,让它在提交页面时不去创建新的验证码,所以我没有在这个CheckImage.aspx中加入创建验证码的指令。
然后在调用该图片的地方插入服务器图片控件,图片地址就是刚才的CheckImage.aspx:<asp:Image ID="Image1" runat="server" ImageUrl="~/CheckImage.aspx" />
在该页面的Load事件中写入:
if (IsPostBack) //当不是第一次载入页面时执行
{
Session["CheckCode"] = CompareValidator1.ValueToCompare;
}
else //否则,即第一次加载页面时执行
{
//创建验证码写入Session(在MackCode()方法里进行),并将其赋给CompareValidator1.ValueToCompare
CompareValidator1.ValueToCompare = CheckCode.MakeCode(Session);
}
重点说一下这句“Session["CheckCode"] = CompareValidator1.ValueToCompare;”,这是在页面回传之后执行的,在第一次载入页面的时候我们把验证码赋予了CompareValidator1.ValueToCompare,而现在我们又把CompareValidator1.ValueToCompare里面的值拿了出来赋给Session,这么做是为了让接下来的图片页面CheckImage.aspx读取到,并根据这个来生成相应的验证码图片,这样做即可解决多页面之间的验证码冲突,也就是说不管当前打开了多少个需要用到验证码的页面,每个页面都将在它们被重新加载的时候向Session写入自己原来的验证码,从而让CheckImage.aspx根据它们原来的验证码去生成图片。
在提交页面的时候都将根据每个页面各自的CompareValidator1.ValueToCompare属性进行判断,而不是公共的Session["CheckCode"] ,Session["CheckCode"] 的作用仅仅是作为表单页面与CheckImage.aspx之间的一个交流通道,表单页面通过Session["CheckCode"] 向CheckImage.aspx下达命令去生产带有指定字符的验证码图片。
当然,使用Session["CheckCode"] 进行页面之间的交互或许并不是那么“地道”,但是由于只是图片地址的引用,而非跨页提交数据,能采用的方法真的很少,想过在Url地址上加上查询字段即“CheckImage.aspx?txt=Ac5I”,可这样无疑又是将验证码明文暴露给客户端了,而Application存储的是公共变量,更不能用,所以只好使用Session来进行了,一般来说同一个浏览器在同一时间同时出发两个调用验证码的页面以致使两边验证码及图片混淆的可能性极其微小,可能两次得提交间隔要小于几微秒,即使实现了估计也是用恶意程序进行的,那就更不用操心他们能不能输入对验证码了。
最后再总结一下实现要点:
1.不要让生成图片的页面自行创建新的验证码
2.让需要使用验证码的页面只在页面的第一次载入时创建验证码
3.页面提交时不要使用Session里的字段作为验证码判断标准,因为很可能在页面载入至提交这段时间内用户已经打开了其他的需要验证码的页面,Session中的验证码已经改变了,所以要使用自己页面内的控件或变量所保存的验证码
4.当页面被重新载入时在Load事件中向Session写入本页面之前保存的验证码,致使生成图片的页面根据这个验证码来产生相应的验证图片,以确保不与其它页面混淆
要实现的验证码的主要目标:可以多个页面同时调用而不会产生冲突。
这是用于生成验证码的类,存为CheckCode.cs:
/// <summary>
/// 用于生成验证码及验证图片
/// </summary>
public class CheckCode
{
public CheckCode()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
/// <summary>
/// 生成随机验证码并写入Session
/// </summary>
public static string MakeCode(System.Web.SessionState.HttpSessionState se)
{
string code = "";
string UseTxt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW XYZ123456789";
System.Random r = new Random();
for (int i = 0; i < 4; i++)
{
code += UseTxt[r.Next(UseTxt.Length)];
}
se.Add("CheckCode", code);
return code;
}
/// <summary>
/// 创建验证图片并输出
/// </summary>
public static void CreateImage(string checkCode, System.Web.HttpResponse re)
{
int iwidth = (int)(checkCode.Length * 19);
System.Drawing.Bitmap image = new System.Drawing.Bitmap(iwidth, 25);
Graphics g = Graphics.FromImage(image);
g.Clear(Color.LightGoldenrodYellow);
//定义颜色
Color[] c = { Color.Black, Color.Red, Color.DarkBlue, Color.Blue, Color.DarkGray, Color.Green, Color.OrangeRed, Color.Brown, Color.DarkCyan, Color.Purple };
//定义字体
string[] font = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial", "Tahoma", "黑体", "幼圆", "宋体" };
Random rand = new Random();
//随机输出噪点
for (int i = 0; i < 50; i++)
{
int x = rand.Next(image.Width);
int y = rand.Next(image.Height);
g.DrawRectangle(new Pen(Color.Orange, 0), x, y, 1, 1);
}
//输出不同字体和颜色的验证码字符
for (int i = 0; i < checkCode.Length; i++)
{
int cindex = rand.Next(c.Length);
int findex = rand.Next(font.Length);
Font f = new System.Drawing.Font(font[findex], 12, System.Drawing.FontStyle.Bold);
Brush b = new System.Drawing.SolidBrush(c[cindex]);
int ii = 2;
if ((i + 1) % 2 == 0)
{
ii = 2;
}
g.DrawString(checkCode.Substring(i, 1), f, b, 3 + (i * 15), ii);
}
//画一个边框
g.DrawRectangle(new Pen(Color.Orange, 0), 0, 0, image.Width - 1, image.Height - 1);
//输出到浏览器
System.IO.MemoryStream ms = new System.IO.MemoryStream();
image.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
re.ClearContent();
re.ContentType = "image/Jpeg";
re.BinaryWrite(ms.ToArray());
g.Dispose();
image.Dispose();
re.End();
}
}
这是绘制为图片供客户端查看的页面CheckImage.aspx,其Load事件内容为:
protected void Page_Load(object sender, EventArgs e)
{
if (Session["CheckCode"] == null) CheckCode.CreateImage("出错了!", this.Response);
else CheckCode.CreateImage(Session["CheckCode"].ToStrin g(), this.Response);
}
这里有个问题,就是我看了很多个网上关于ASP.net验证码的文章,都是让这个图片页自己创建随机的验证码、写入Session然后再输出为图片。
可是这样做有两个弊病,一是倘若用户同时打开多次同一个页面或者打开多个都需要验证码的页面时,不管前几个页面看到的验证图片是什么,都只有输入最后一次打开的页面的验证码才算正确,等于同一时间内多个页面之间只有一个页面的真实的验证码和图片能对应上;
另一个问题是,如果让这个页面控制创建验证码的话,势必每次刷新或提交页面都会被要求输入不同的验证码,如果使用数据验证控件CompareValidator进行验证的话(需要关闭客户端验证,开启的话等于是把验证码明文放在网页源代码里给客户端),我设置的控件属性为:
<asp:CompareValidator
ID="CompareValidator1" runat="server" ControlToValidate="TextBox3" Display="Dynamic"
EnableClientScript="False" ErrorMessage="验证码错误">验证码错误</asp:CompareValidator>
然后在该页面的Load事件中将Session中的"CheckCode"字段读出赋给CompareValidator1的ValueToCompare属性,以给其判断依据。
但这样每次不管你输入的是否正确,都会验证失败,我尝试在提交表单的按钮的Click事件中获取Session["CheckCode"]以及CompareValidator1.ValueToCompare的值,发现每次提交的时候获取的值都是下一次页面刷新后所得的验证码图片中所显示的字符,我想了很多种方法也没解决这个问题,感觉好像是当用户按下提交按钮时服务器要控制页面先去进行各种控件的载入和初始化,然后再去执行提交按钮的事件,所以没办法了,只有控制验证码,让它在提交页面时不去创建新的验证码,所以我没有在这个CheckImage.aspx中加入创建验证码的指令。
然后在调用该图片的地方插入服务器图片控件,图片地址就是刚才的CheckImage.aspx:<asp:Image ID="Image1" runat="server" ImageUrl="~/CheckImage.aspx" />
在该页面的Load事件中写入:
if (IsPostBack) //当不是第一次载入页面时执行
{
Session["CheckCode"] = CompareValidator1.ValueToCompare;
}
else //否则,即第一次加载页面时执行
{
//创建验证码写入Session(在MackCode()方法里进行),并将其赋给CompareValidator1.ValueToCompare
CompareValidator1.ValueToCompare = CheckCode.MakeCode(Session);
}
重点说一下这句“Session["CheckCode"] = CompareValidator1.ValueToCompare;”,这是在页面回传之后执行的,在第一次载入页面的时候我们把验证码赋予了CompareValidator1.ValueToCompare,而现在我们又把CompareValidator1.ValueToCompare里面的值拿了出来赋给Session,这么做是为了让接下来的图片页面CheckImage.aspx读取到,并根据这个来生成相应的验证码图片,这样做即可解决多页面之间的验证码冲突,也就是说不管当前打开了多少个需要用到验证码的页面,每个页面都将在它们被重新加载的时候向Session写入自己原来的验证码,从而让CheckImage.aspx根据它们原来的验证码去生成图片。
在提交页面的时候都将根据每个页面各自的CompareValidator1.ValueToCompare属性进行判断,而不是公共的Session["CheckCode"] ,Session["CheckCode"] 的作用仅仅是作为表单页面与CheckImage.aspx之间的一个交流通道,表单页面通过Session["CheckCode"] 向CheckImage.aspx下达命令去生产带有指定字符的验证码图片。
当然,使用Session["CheckCode"] 进行页面之间的交互或许并不是那么“地道”,但是由于只是图片地址的引用,而非跨页提交数据,能采用的方法真的很少,想过在Url地址上加上查询字段即“CheckImage.aspx?txt=Ac5I”,可这样无疑又是将验证码明文暴露给客户端了,而Application存储的是公共变量,更不能用,所以只好使用Session来进行了,一般来说同一个浏览器在同一时间同时出发两个调用验证码的页面以致使两边验证码及图片混淆的可能性极其微小,可能两次得提交间隔要小于几微秒,即使实现了估计也是用恶意程序进行的,那就更不用操心他们能不能输入对验证码了。
最后再总结一下实现要点:
1.不要让生成图片的页面自行创建新的验证码
2.让需要使用验证码的页面只在页面的第一次载入时创建验证码
3.页面提交时不要使用Session里的字段作为验证码判断标准,因为很可能在页面载入至提交这段时间内用户已经打开了其他的需要验证码的页面,Session中的验证码已经改变了,所以要使用自己页面内的控件或变量所保存的验证码
4.当页面被重新载入时在Load事件中向Session写入本页面之前保存的验证码,致使生成图片的页面根据这个验证码来产生相应的验证图片,以确保不与其它页面混淆
相关文章推荐
- Scott Mitchell 的ASP.NET 2.0数据教程之十二:在GridView控件中使用TemplateField
- 使用ASP.NET 2.0中的GridView控件
- ASP.NET 2.0中使用sitemapdatasource页面导航控件
- ASP.NET 2.0使用FileUpload控件上传文件示例
- ASP.NET 2.0 中FileUpload上传控件的使用
- Asp.net 2.0 自定义控件开发专题[详细探讨页面状态(视图状态和控件状态)机制及其使用场景](示例代码下载)
- 在ASP.NET 2.0中操作数据之十二:在GridView控件中使用TemplateField
- ASP.NET 2.0中使用webpart系列控件
- 在ASP.NET 2.0中操作数据之十二:在GridView控件中使用TemplateField
- ASP.NET 2.0数据教程之十二:在GridView控件中使用TemplateField(转)
- 【转贴】使用ASP.NET 2.0 DetailsView控件处理数据
- 使用ASP.NET 2.0中的GridView控件
- [导入]使用 ASP.NET 2.0 ObjectDataSource 控件
- Scott Mitchell 的ASP.NET 2.0数据教程之十三:在DetailsView控件中使用TemplateField
- ASP.NET 2.0 Login控件使用技巧(一) - 迁徙数据库,个性化Login控件
- 技巧和诀窍:使用ASP.NET 2.0 CSS 控件适配器生成CSS友好的HTML输出
- 使用ASP.NET 2.0中的GridView控件
- Asp.net 2.0 自定义控件开发专题[详细探讨页面状态(视图状态和控件状态)机制及其使用场景](示例代码下载)
- 使用 ASP.NET 2.0 ObjectDataSource 控件
- (转)ASP.NET 2.0:使用用户控件和定制的Web部件个人化你的门户网站(一)