最容易使用的验证码控件(转载)

 下面给大家介绍一个最容易使用的验证码控件CheckBot。为吗说是最容易使用呢,因为我看到过的验证码控件都需要在IIS上做不少设置,或者还要拷贝一些特殊的文件到特定的目录,而且验证码图像与输入验证码的输入框外观设置起来也很麻烦(甚至你根本没法设置),而CheckBot则象标准控件一样,无需任何设置,而外观设置则遵循标准控件的统一标准。

让我来演示一下,首先你要把它放到工具箱里去,你要下载那个dll文件(home.goofar.com/gugu2000cec里去找找看),然后在工具箱上点右键,然后点浏览选择下载的dll文件,然后CheckBot就应该+到你的工具箱了去了。

新建1个apsnet页,把CheckBot拖过去,你看到的应该是只是一个小图片,而没有收集用户输入的输入框。验证码的图像和验证码输入框是分离的,这样设计的好处是很方便设置验证码这2部分的布局,你可以关联页面上任何一个可以收集用户输入文字的控件作为验证码输入框。下面我说说如何让CheckBot关联一个收集用户输入文字的控件作为验证码输入框,现拖一个TextBox控件到页面上,然后查看CheckBot的属性窗口中的ControlForUserType属性,这是一个下拉框,点击一下,你就可以看到刚刚拖出来的TextBox控件就在列表里,实际上这个列表列出了页面上全部可以做为验证码输入框的控件。这样第1步就设置完成了,你可以点下F5看到效果了。是不是很容易呢,验证用户输入是否正确也很容易,下一步再拖一个Button到页面上,双击他跳转到它的处理程序,假设添加的CheckBot控件的ID为CheckBot1,添加这句代码 Response.Write(CheckBot1.Validate());然后再按F5运行,当你在输入框中输入正确的验证码时,点击了Button会输出True,如果写错了,当然会输出False,也许你已经明白了,在代码中调用CheckBot1.Validate()就可以获得用户输入的验证码是否匹配。

通过上边的介绍,你是否也同意CheckBot很容易使用呢,实际上CheckBot不仅容易使用,而且很容易扩展,还方便部署,下边现给打家介绍下如何扩展验证码的绘制。让我猜下你刚刚对CheckBot的感受,第1是它的确挺容易的,第2应该是它画出来的验证码图像太搓了吧,-_-!!真是不好意思,我还没找到个画验证码好法子,不过你完全可以方便的用你自己的方法来画验证码。到刚才的页面去,双击下验证码图像,出现了protected void CheckBot1_DrawText(object sender, Gugu.CheckBotComponent.CheckBotDrawTextEventArgs e){},在这个事件里你可以画验证码,事件参数e中包括了画验证码所必需的所有参数,e.Text就是要画的验证码文字,e.Graphics是绘图环境上下文,e.Graphics.DrawString()方法是很便利使用的,e.Rectangle 则描绘了绘图表面的大小,还有些别的参数你可以从属性窗口查一下描述,相信你一定能画个比我强的多的验证码。最后要提醒一下,CheckBot控件无法获知是否你在这个事件里什么也没做,如果真是那样,验证码图像会变成个全黑的图片,如果你想让其还原,则需要把刚才处理CheckBot1_DrawImageText()这个事件的代码全部删除。

既然画验证码的方法可以自己定制,那么验证码的中文字是否也可以自己定制呢?那是当然的了,CheckBot.ImageText属性就是验证码中将绘制的文字,你可以用自己的方法每次赋一个随机的字符串值,千万别总是赋一个固定值给它,如果那样每次验证码的文字都一样啦。CheckBot.ImageText属性的默认值为null,在其设置为null的情况下,CheckBot会自动生成随机的文字。

上边我说了CheckBot有一个DrawText事件,实际是CheckBot还有另外一个事件:PreDrawText。为吗CheckBot需要2个事件呢?这和CheckBot的工作原理是分不开的。我们知道1次Http请求,只能得到一种类型的Meta类型的回复,像输入框这种元素,是文本类型的,而验证码的图片,则是图像类型的,2者的类型不一样,所以一个完整的验证码不能够用1次Http请求完成,实际上是2次。第1次请求获得输入框和第2次请求的参数,第2次请求获得验证码图像。为了把第2次请求时页面不用处理的数据减到最小,在CheckBot的初始化过程中,CheckBot就完成了画图并把页面终止,所以写在其他位置的代码还没有执行,页面就结束了。但是在第2次请求图像,CheckBot画图前会先后触发PreDrawText和DrawText2个事件,你可以在这个最后时刻做一些工作。那么在PreDrawText中都可以做些啥工作呢?修改CheckBot.Drawer属性,Drawer是实现了Gugu.CheckBotComponent.ICheckBotDrawer接口的一个类,实现这个接口的类都要实现DrawText()方法,而此方法可以实现一个画验证码的引擎。这样,你可以把画验证码的引擎的代码与页面的代码分离,可以放到某一个模块里,或者来自另一个程序集。请注意:如果你想让刚刚在PreDrawText中赋值的CheckBot.Draw属性起作用,那一个不要截获DrawText事件,因为如果DrawText事件的监听不为空,CheckBot就会认为你要自己手动来画验证码,那么就会忽略CheckBot.Draw属性。

前边刚给大家介绍了CheckBot的一个主要组件:CheckBot.Draw,实际CheckBot共有4个主要组件,另3个分别是CheckBot.Manager,CheckBot.Generator,CheckBot1.UrlCodes,这4部分都是可以分别订制,从而扩展你的验证码控件。CheckBot.Manager是用来生成随机的验证码文字的,CheckBot1.UrlCodes则管理者着验证码图像中的参数的编码,从而实现第1次和第2次Http请求之间的信息交换,你可以随时用分别实现了Gugu.CheckBotComponent.ICheckBotGenerator接口和Gugu.CheckBotComponent.CheckBotUrlCodes接口的类更改验证码的行为方式。CheckBot.Draw是画验证码的引擎。最后CheckBot.Manager是最复杂的一个,它用来保存验证码的秘密,使客户端的计算机无法识别,同样对CheckBot.Manager属性的更改也有一点特殊的要求,因为第2次请求中画验证码图像是在init初始化中就完成了,所以要想更改CheckBot.Manager属性,必须在init事件之前完成,所以如果要更改CheckBot.Manager属性,务必把它写在Page.PreInint事件中。

CheckBot的确是有很大的灵活性,如果有1天,你在你的站点上很多个不同页面使用了CheckBot,但是某1天发现了有人写了1个更好的验证码画图引擎,并发布为了一个程序集dll文件,你想立即把站点上的所有CheckBot都使用新引擎,那么可以高兴得告诉你,你只需要改几个字母,并不需要修改全部这些页面就可以完成这个庞大的工作,CheckBot支持方便的部署。你可以在站点的web.config文件中的<appSettings>中添加<add key="CheckBotFactoryAssemblyName" value="NewCheckBot"/><add key="CheckBotFactoryAssemblyVersion" value="1.0.0.0"/><add key="CheckBotFactoryClassName" value="MyNameSpace.MyCheckBotFactory"/>这3个参数,这3个参数指定了这个新程序集中实现了Gugu.CheckBotComponent.ICheckBotFactory接口的类,最简单的情况下,就是程序集里只一个实现Gugu.CheckBotComponent.ICheckBotFactory接口的类,这种情况下你只添加<add key="CheckBotFactoryAssemblyName" value="NewCheckBot"/>这一个参数就可以了,在去各个页面看一下验证码,所有页面的验证码图像都已经改变了。

再大家聊聊验证码的细节,验证码的原理是客户发出第1个请求,服务器端生成一个随机字符串,和对应这个字符串的令牌,然后把令牌保存到回送的页面中,同时把令牌编码到第2次请求的Url参数里,这样第1次请求处理完毕,回送的页面包含了令牌和第2次请求的Url参数,而Url参数里也有同一个令牌;客户端获得第1次请求的回送后就开始第2次请求,服务器端处理第2次请求,并从Url参数里获得令牌,并通过此令牌查询对应的先期生成的字符串,然后画图,并把图像返回客户端;用户识别图像,并填写验证码,然后提交,服务器接受后,根据保存在页面中的令牌再次查询到先期生成的字符串,并与用户输入的进行比较,从而可以判断出用户输入是否正确。可以看到在整个过程中客户端只获得一个令牌,而这个令牌并不包含对应字符串的任何信息,所以客户端的计算机无法通过令牌来识别验证码,唯一识别验证码的途径是识别第2次返回的图像,如果图像画的足够花里胡哨的话,客户端的计算机又识别不了,只能由人来识别,从而起到了预防机器人的作用。

话虽这么说,可是在服务器端保存1大堆的字符串还是很占资源的啊,所以CheckBot的默认实现并没有在服务器端保存字符串,而是把它加密了放到令牌里去了,这样的好处是解放了服务器,坏处自是不用说,危险重重啊。不过把秘密放在客户端也不是一无是处,虽然客户端可以拿着这个令牌来冒充,但是我们还是有对付他的方法,方法就是过期。CheckBot.ExpectantPeriod就是用来设施过期时间的,初始为5分钟。对CheckBot的默认实现来说就是保存到客户端的令牌只有5分钟的有效期,如果有人拿着一个冒充的令牌大发淫威的话,他只能疯狂5分钟,5分钟后他得再去自己识别另一个图片,所以客户端保存秘密如果受到攻击,最多是个半自动的,而不会是全自动的。

如果你决定让半自动的的攻击也不可能,就可以把秘密保存到服务器端,在Gugu.CheckBotComponent名称空间下有另外一个类:CheckBotManageerSaveAtServer,它就是把秘密保存到了服务器端,你可以在Page.PreInit事件中把CheckBot.Manager属性设置为CheckBotManageerSaveAtServer,字符串的秘密的保存就要辛苦服务器了。

前边提到了CheckBot.ExpectantPeriod属性,你可能会奇怪怎么不直接叫Period,而叫ExpectantPeriod,因为对秘密保存在服务器端的验证码可以保证几年也不过期,而对把秘密保存在客户端验证码则必须很快过期,所以具体的过期策略最终是由CheckBot.Manager来管理的,所以就成了期望的过期时间,CheckBot中以Expectant大头的属性还有几个,原因和这差不多。

既然有了过期时间,那么在验证用户输入验证码失败就有2种情况了,可能是输入错了,也可能是验证码过期造成的,那对于页面作者来说能区分这2种情况吗?当然能啊,CheckBot.ValidatedTimeOut是一个bool属性,如果他返回为false,就说明验证失败失败是由于验证码过期造成的。

某人说说起来是1回事,做起来是另会事,真是如此啊,再让我说下去是我设想的1大堆不足或错误还有构想,好像怎么也改不完,欢迎你使用这个验证码控件,有任何意见或建议请致邮件gugu2000cec(AT)msn.com,编译了的dll和cs文件应该可以在home.goofar.com/gugu2000cec找到(要是没有你就隔2天再找)。后边是代码。


//***********************代码开始**************************************//


using System;
using System.Collections.Generic;
using System.Text;

//控件名称:Gugu.CheckBot --验证码 (.Net版本:2.0)
//版本:1.0.0.0
//作者:Gugu
//邮箱:gugu2000cec(AT)msn.com
//时间:2006-9-5

namespace Gugu
{
#region CheckBot

//存在的首要问题:返回验证码图像的调用并没有像设想的那样在 init 事件后终止,猜测是因为权限不足造成的,一个控件怎么有权力终止一个页面的呈现呢。
//测试中曾出现了出个黑方框未知异常。

/// <summary>
/// 验证码控件
/// </summary>
[System.ComponentModel.DefaultProperty("ControlForUserType")]
[System.ComponentModel.DefaultEvent("DrawImageText")]
[System.ComponentModel.Designer (typeof(Gugu.CheckBotComponent.CheckBotDesigner))]
[System.ComponentModel.Description("验证码控件")]
public sealed class CheckBot:System.Web.UI.WebControls.Image
{
#region Const

private const string GUID = "fdcd854648e1478f808046474654c7c5";

/// <summary>
/// 验证码图像允许的最大宽度。
/// </summary>
public const int ImageMaxWidth = 800;

/// <summary>
/// 验证码图像允许的最小宽度。
/// </summary>
public const int ImageMinWidth = 50;

/// <summary>
/// 验证码图像允许的最大高度。
/// </summary>
public const int ImageMaxHeight = 600;

/// <summary>
/// 验证码图像允许的最小高度。
/// </summary>
public const int ImageMinHeight = 25;

#endregion

#region 创建模式部分

private static Gugu.CheckBotComponent.ICheckBotFactory Factory;

static CheckBot ()
{
//异常处理:
//已预见的异常有4处:
//1. *AssemblyName 输入错误,系统会找不到文件,触发 System.IO.FileNotFoundException 异常。
//2. *AssemblyVersion 版本输入错误,系统无法引导正确的程序集,触发 System.IO.FileLoadException 异常。
//3. *Factory 设置的类没有实现 I*Factory 接口时会触发 System.InvalidCastException 异常。
//前三种异常系统的默认说明清楚明白,我就不用再捕获了。
//4. *Factory 类名称输入错误,系统会把 *Factory 设置为 Null,或者没有输入 *Factory 的情况下,CheckBot 控件自动查找的过程中没有找到相应的实现了接口的类,
//也会把 *Factory 设置为 Null,在 static CheckBot ()中不会触发异常,但会在别的代码中触发 System.NullReferenceException 异常,这个异常会使用户很困惑,
//所以应该捕获第4种异常并给出相应说明 。

//强名处理?
//国际化的支持?

const string an = "CheckBotFactoryAssemblyName";
const string av = "CheckBotFactoryAssemblyVersion";
const string cn = "CheckBotFactoryClassName";

if (System.Web.Configuration.WebConfigurationManager.AppSettings[an] != null)
{
System.Reflection.AssemblyName assemblyName = new System.Reflection.AssemblyName(System.Web.Configuration.WebConfigurationManager.AppSettings[an]);
if (System.Web.Configuration.WebConfigurationManager.AppSettings[av] != null)
assemblyName.Version = new System.Version(System.Web.Configuration.WebConfigurationManager.AppSettings[av]);
System.Reflection.Assembly assembly = System.Reflection.Assembly.Load(assemblyName);
if (System.Web.Configuration.WebConfigurationManager.AppSettings[cn] != null)
{
CheckBot.Factory = (Gugu.CheckBotComponent.ICheckBotFactory)
assembly.CreateInstance(System.Web.Configuration.WebConfigurationManager.AppSettings[cn]);
if (CheckBot.Factory == null)
throw new Gugu.CheckBotComponent.CheckBotException("WebConfig 中 CheckBotFactory 设置的 "
+ System.Web.Configuration.WebConfigurationManager.AppSettings[cn] + " 实例化为 ICheckBotFactory 失败。");
}
else//自动查找
{
System.Type[] types = assembly.GetTypes();
foreach (System.Type type in types)
{
System.Type[] interfaces = type.GetInterfaces();
foreach (System.Type interface_ in interfaces)
{
if (interface_ == typeof(Gugu.CheckBotComponent.ICheckBotFactory))
{
CheckBot.Factory = (Gugu.CheckBotComponent.ICheckBotFactory)assembly.CreateInstance(type.FullName);
break;
}
}
}
if (CheckBot.Factory == null)
{
string str = "/"" + System.Web.Configuration.WebConfigurationManager.AppSettings[an];
if (System.Web.Configuration.WebConfigurationManager.AppSettings[av] != null)
str += " Version=" + System.Web.Configuration.WebConfigurationManager.AppSettings[av];
str += "/" 中没有实现 ICheckBotFactory 接口的类。";
throw new Gugu.CheckBotComponent.CheckBotException(str);
}
}
}
else
CheckBot.Factory = new Gugu.CheckBotComponent.CheckBotFactory();
}

/// <summary>
/// 初始化验证码控件
/// </summary>
public CheckBot()
{
//Gugu.CheckBotComponent.CheckBotManager temp = new Gugu.CheckBotComponent.CheckBotManager();
//manager_ = temp;
//image_url_codes = temp;
}

private Gugu.CheckBotComponent.ICheckBotGenerator generator_ = CheckBot.Factory.GetCheckBotGenerator();

/// <summary>
/// 管理生成随机文字
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("管理生成随机文字")]
public Gugu.CheckBotComponent.ICheckBotGenerator Generator
{
get
{
if (this.generator_ == null)
this.generator_ = new Gugu.CheckBotComponent.CheckBotGenerator();
return this.generator_;
}
set { this.generator_ = value; }
}

private Gugu.CheckBotComponent.ICheckBotDrawer drawer_ = CheckBot.Factory.GetCheckBotDrawer();

/// <summary>
/// 管理绘制验证码文字
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("管理绘制验证码文字")]
public Gugu.CheckBotComponent.ICheckBotDrawer Drawer
{
get
{
if (this.drawer_ == null)
this.drawer_ = new Gugu.CheckBotComponent.CheckBotDrawer();
return this.drawer_;
}
set { this.drawer_ = value; }
}

private Gugu.CheckBotComponent.ICheckBotUrlCode image_url_codes = CheckBot.Factory.GetCheckBotImageUrlCodes();

/// <summary>
/// 管理通过 Url 传递验证码图像参数
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("管理通过 Url 传递验证码图像参数")]
public Gugu.CheckBotComponent.ICheckBotUrlCode UrlCode
{
get
{
if (this.image_url_codes == null)
this.image_url_codes = new Gugu.CheckBotComponent.CheckBotUrlCodes();
return this.image_url_codes;
}
set { this.image_url_codes = value; }
}

private Gugu.CheckBotComponent.IChectBotManager manager_ = CheckBot.Factory.GetCheckBotManager();

/// <summary>
/// 管理验证码文字的存储
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("管理验证码文字的存储")]
public Gugu.CheckBotComponent.IChectBotManager Manager
{
get
{
if (this.manager_ == null)
{
this.manager_ = new Gugu.CheckBotComponent.CheckBotManager();
}
return this.manager_;
}
set { this.manager_ = value; }
}

#endregion

#region 属性

#region public string ControlForUserType

private string control_for_user_type_id;

[System.Web.UI.IDReferenceProperty()]
[System.ComponentModel.TypeConverter(typeof(Gugu.CheckBotComponent.CheckBotUserTypeControlConvert))]
[System.ComponentModel.Category("Behavior")]
[System.ComponentModel.Description("收集用户输入验证码的控件。")]
public string ControlForUserType
{
//[System.Web.UI.IDReferenceProperty()]属性的设置方法还不清楚
get { return this.control_for_user_type_id; }
set { control_for_user_type_id = value; }
}

#endregion

#region public override string ImageUrl [屏蔽]

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
[System.ComponentModel.Browsable(false)]
public override string ImageUrl
{
//此屏蔽属性是公开的,会不会客户代码来修改此属性造成安全隐患呢?回答是否定的,在最终的 Render() 前会将此属性最终赋值,通过其他方法修改的此属性值都将被覆盖。
get { return base.ImageUrl; }
set { base.ImageUrl = value; }
}

#endregion

#region public int ExpectantPeriod

private int expectant_period = 300;

/// <summary>
/// 期望的验证码过期时间,以秒为单位,最终的验证码过期策略由实现 IManager 接口的实例管理。
/// </summary>
[System.ComponentModel.Category("Behavitor")]
[System.ComponentModel.Description("期望的验证码过期时间,以秒为单位。")]
public int ExpectantPeriod
{
get { return expectant_period; }
set { expectant_period = value; }
}

#endregion

#region public string ImageText

private string image_text = null;

/// <summary>
/// 将要在验证码图像中呈现的文字,设置为null则用随机文字自动填充。
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("将要在验证码图像中呈现的文字,设置为null则用随机文字自动填充。")]
public string ImageText
{
get
{
if (this.image_text != null)
return this.image_text;
return this.Generator.GeneratImageText(this.expect_image_text_length);
}
set { image_text = value; }
}

#endregion

#region public int ImageWidth

private int image_width = CheckBot.ImageMinWidth;

/// <summary>
/// 验证码图像的宽度,以像素为单位,不超过800,不小于?。
/// </summary>
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.Description("验证码图像的宽度。")]
public int ImageWidth
{
get { return image_width; }
set
{
if (CheckBot.ImageMinWidth <= value && value <= CheckBot.ImageMaxWidth)
image_width = value;
}
}

#endregion ImageWidth

#region public int ImageHeight

private int image_height = CheckBot.ImageMinHeight;

/// <summary>
/// 验证码图像的高度,以像素为单位,不超过600,不小于?。
/// </summary>
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.Description("验证码图像的高度,以像素为单位。")]
public int ImageHeight
{
get { return image_height; }

set
{
if (CheckBot.ImageMinHeight <= value && value <= CheckBot.ImageMaxHeight)
image_height = value;
}
}

#endregion ImageHeight

#region public System.Drawing.Color ExpectantImageBackColor

private System.Drawing.Color expect_image_back_color = System.Drawing.Color.White;

/// <summary>
/// 期望的验证码图像中背景色
/// </summary>
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.Description("期望的验证码图像中背景色。")]
public System.Drawing.Color ExpectantImageBackColor
{
get { return expect_image_back_color; }
set { expect_image_back_color = value; }
}

#endregion

#region public System.Drawing.Color ExpectantImageTextColor

private System.Drawing.Color expect_image_text_color = System.Drawing.Color.Black;

/// <summary>
/// 期望的验证码图像的文字色彩
/// </summary>
[System.ComponentModel.Category("Appearance")]
[System.ComponentModel.Description("期望的验证码图像的文字色彩。")]
public System.Drawing.Color ExpectantImageTextColor
{
get { return expect_image_text_color; }
set { expect_image_text_color = value; }
}

#endregion

#region public int ExpectantImageTextLength

private int expect_image_text_length = 4;

/// <summary>
/// 期望的验证码图片文字的字符数
/// </summary>
[System.ComponentModel.Category("Behavitor")]
[System.ComponentModel.Description("期望的验证码图片文字的字符数。")]
public int ExpectantImageTextLength
{
get { return expect_image_text_length; }
set { expect_image_text_length = value; }
}


#endregion

#region public bool ValidatedTimeOut

private bool validated_time_out = false;

/// <summary>
/// 当调用 Validate() 返回 false 后,如是因为验证码超时造成验证失败,此属性值将被设置为 true,其他状态下其均为 false.
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("当调用 Validate() 返回 false 后,如是因为验证码超时造成验证失败,此属性值将被设置为 true,其他状态下其均为 false.")]
public bool ValidatedTimeOut
{
get { return validated_time_out; }
}

#endregion

#region public object OtherImageArgs

private object other_image_args = null;

/// <summary>
/// 可扩展参数接口,必须与内部扩展配合使用。
/// </summary>
[System.ComponentModel.Browsable(false)]
[System.ComponentModel.Description("可扩展参数接口,必须与内部扩展配合使用。")]
public object OtherImageArgs
{
get { return other_image_args; }
set { other_image_args = value; }
}

#endregion

#region private string User_Type

//获取用户输入的验证码
//有待改进
private string User_Type
{
//这里使用的 findcontrol + 类型强制转换 是否是最优方法呢?
get
{
System.Web.UI.Control c = this.Page.FindControl(this.control_for_user_type_id);
try
{
System.Type t = c.GetType();
System.Web.UI.WebControls.TextBox textbox = c as System.Web.UI.WebControls.TextBox;
if (textbox != null)
return textbox.Text;
System.Web.UI.HtmlControls.HtmlInputText inputtext = c as System.Web.UI.HtmlControls.HtmlInputText;
if (inputtext != null)
return inputtext.Value;
System.Web.UI.HtmlControls.HtmlInputPassword pwd = c as System.Web.UI.HtmlControls.HtmlInputPassword;
if (pwd != null)
return pwd.Value;
System.Web.UI.HtmlControls.HtmlTextArea textarea = c as System.Web.UI.HtmlControls.HtmlTextArea;
if (textarea != null)
return textarea.Value;
throw new Gugu.CheckBotComponent.CheckBotException
("从/"" + this.UniqueID + "/"的/"ControlForUserType/"属性指定的控件/"" + this.control_for_user_type_id + "/"无法获取用户输入的验证码。");
//Control 'Image1' referenced by the ControlToValidate property of 'RequiredFieldValidator1' cannot be validated.
}
catch (System.NullReferenceException)
//System.NullReferenceException 在 [System.Type t = c.GetType()] 语句在 [c == null] 时触发。当 c = Page.FindControl(id) 没有找到控件时返回 null
{
if (this.control_for_user_type_id == null || this.control_for_user_type_id == System.String.Empty)
throw new Gugu.CheckBotComponent.CheckBotException ("/"" + this.UniqueID + "/"的/"ControlForUserType/"属性不能为空。");
//The ControlToValidate property of 'RequiredFieldValidator1' cannot be blank.
else
throw new Gugu.CheckBotComponent.CheckBotException
("无法为/"" + this.UniqueID + "/"的/"ControlForUserType/"属性找到ID为/"" + this.control_for_user_type_id + "/"的控件。");
//Unable to find control id '1111' referenced by the 'ControlToValidate' property of 'RequiredFieldValidator1'.
}
}
}

#endregion

private object token_ = null;

#endregion

#region events

/// <summary>
/// 绘制验证码图像。
/// </summary>
public event Gugu.CheckBotComponent.CheckBotDraTextEventHandler DrawText;

/// <summary>
/// 在绘制验证码图像之前发生此事件。
/// </summary>
public event System.EventHandler PreDrawText;

#endregion

#region override OnInit()

protected override void OnInit(EventArgs e)
{
//判断是否应该去画图片
if (this.Page.Request.QueryString.Count == 2 && this.Page.Request.QueryString.GetKey(0) == CheckBot.GUID && this.ClientID == this.Page.Request.QueryString[0])
{
//这里有一个技术,可以检查请求这个页面的上一个页面是否是也是本页面,如果不是,我应该啥也不显示。但是我对此技术的细节不太清楚,所以现不使用。
//if(this.Page.Request.UrlReferrer.
//另一个需要处理的问题是 这里 有外部 传入的数据,可能会受到 攻击 而出现异常,需要防护的方法。
System.Drawing.Graphics g = null;
System.Drawing.Bitmap bitmap = null;
try
{
Gugu.CheckBotComponent.ImageUrlCodesArgs p = this.UrlCode.Decode(this.Page.Request.QueryString[1]);
this.ImageWidth = p.ImageWidth;
this.ImageHeight = p.ImageHeight;
string text = this.Manager.PeekImageText(p.Identification);
if (text == null)
throw new Gugu.CheckBotComponent.CheckBotException("画图前验证码图像文字不能为空。");
if (this.PreDrawText != null)
this.PreDrawText(this, System.EventArgs.Empty);
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, this.ImageWidth, this.ImageHeight);
bitmap = new System.Drawing.Bitmap(this.ImageWidth, this.ImageHeight);
g = System.Drawing.Graphics.FromImage(bitmap);
if (this.DrawText != null)
this.DrawText(this, new Gugu.CheckBotComponent.CheckBotDrawTextEventArgs(text, rect, g,p.ExpectTextBackColor,p.ExpectTextColor,p.ExPara));
else
{
Gugu.CheckBotComponent.CheckBotDrawTextArgs dp = new Gugu.CheckBotComponent.CheckBotDrawTextArgs(text, rect, g,p.ExpectTextBackColor,p.ExpectTextColor,p.ExPara);
this.Drawer.DrawText(dp);
}
}
catch (Gugu.CheckBotComponent.CheckBotException)
{
if (g != null)
g.Dispose();
if (bitmap != null)
bitmap.Dispose();
bitmap = new System.Drawing.Bitmap(100, 100);
g = System.Drawing.Graphics.FromImage(bitmap);
//try
//{
System.Drawing.Font f = new System.Drawing.Font("Arial", 12);//测试结果是:如果系统没有安装此字体,会自动设置到一个默认字体,所以这里异常可能不会发出。
//}
//catch
//{
// throw new System.SystemException("操作系统中未安装Arial字体。");
//}
g.DrawString("验证码内部出错!", f, System.Drawing.Brushes.Red, 100, 100);
}
this.Page.Response.Clear();
this.Page.Response.ContentType = "image/jpeg";
System.Drawing.Imaging.ImageCodecInfo ici = null;
System.Drawing.Imaging.ImageCodecInfo[] codecs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders();
foreach (System.Drawing.Imaging.ImageCodecInfo codec in codecs)
{
if (codec.FormatDescription == "JPEG")
{
ici = codec;
}
}
if (ici == null)
throw new System.SystemException("操作系统中未安装JPEG图像编码。");
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.ColorDepth;
System.Drawing.Imaging.EncoderParameter ep = new System.Drawing.Imaging.EncoderParameter(encoder, 25L);
System.Drawing.Imaging.EncoderParameters eps = new System.Drawing.Imaging.EncoderParameters(1);
eps.Param[0] = ep;
bitmap.Save(this.Page.Response.OutputStream, ici, eps);
g.Dispose();
bitmap.Dispose();
this.Page.Response.OutputStream.Flush();
this.Page.Response.End();
}
this.Page.RegisterRequiresControlState(this);
base.OnInit(e);
}

#endregion

#region override SaveControlState()

protected override object SaveControlState()
{
if (!this.Page.IsPostBack)//初次呈现生成新的验证码
{
this.token_ = this.Manager.SaveImageText(this.ImageText, System.TimeSpan.FromSeconds(this.expectant_period));
}
else
{
if (this.validated_)//是否调用了 Validate(),调用过就重新生成验证码
{
this.Manager.DeleteImageText(this.token_);
this.token_ = this.Manager.SaveImageText(this.ImageText, System.TimeSpan.FromSeconds(this.expectant_period));
}
else//未调用 Validate() 则维持验证码并更新过期时间
{
object o = this.Manager.UpdatePeriod(this.token_, System.TimeSpan.FromSeconds(this.ExpectantPeriod));
if (o == null)//更新过期时间返回 null 表示已经过期,所以更新失败,则重新生成验证码
{
this.Manager.DeleteImageText(this.token_);
this.token_ = this.Manager.SaveImageText(this.ImageText, System.TimeSpan.FromSeconds(this.expectant_period));
}
else
this.token_ = o;
}
}
return this.token_;
}

#endregion

#region override LoadControlState()

protected override void LoadControlState(object savedState)
{
this.token_ = savedState;
}

#endregion

#region override Render()

protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
Gugu.CheckBotComponent.ImageUrlCodesArgs a = new Gugu.CheckBotComponent.ImageUrlCodesArgs();
a.ImageWidth = this.ImageWidth;
a.ImageHeight = this.ImageHeight;
a.Identification = this.token_;
a.ExpectTextBackColor = this.expect_image_back_color;
a.ExpectTextColor = this.expect_image_text_color;
a.ExPara = this.other_image_args;
System.Text.StringBuilder sb = new StringBuilder(128);
sb.Append(this.Page.Request.Url.AbsolutePath).Append("?").Append(CheckBot.GUID).Append("=").Append(this.ClientID).Append("&=");
sb.Append(this.UrlCode.Encode(a));
this.ImageUrl = sb.ToString();
base.Render(writer);
}

#endregion

#region public bool Validate()

/// <summary>
/// 标示是否调用过 Validate
/// </summary>
private bool validated_ = false;

/// <summary>
/// 用户输入是否与图像文字匹配,相匹配为 true,反之为 false,同时更新图像文字。
/// </summary>
[System.ComponentModel.Description("用户输入是否与图像文字匹配,相匹配为 true,反之为 false,同时更新图像文字。")]
public bool Validate()
{
#region 同时更新图像文字的理由

//同时更新图像文字的理由:
//如果图像文字总不更新,则验证码面临暴力破解的危险,这种情况是一个不能容忍的错误,所以通过某种方式更新图像文字是必须。
//如果此处不更新图像文字,可以由用户代码中通过调用 ChangeImageText() 手动更新图片文字,并且这样做还有个好处是可以由用户实现更新图像文字的策略;
//但同时这个好处也是坏处,那就是用户代码必须实现更新图像文字的策略,如果其不设置或忘了设置,就会出现图像文字总不更新的错误。
//我认为更新图像文字的策略并不是很重要,比如一个用户填错三次就更新图像文字的策略与每验证一次都更新图像文字的策略没有本质的区别,
//所以我认为绝大多数情况下大家都可以接受每验证一次都更新图像文字的策略。
//最后,还有一种方式是我将每次验证都更新图像文字作为默认策略,然后设置一个标示来区别是否用户希望实现自己的策略,这很容易实现,
//我可以添加一个 public bool AutoUpdata 属性,如为 true,此处就更新图像文字,反之就不更新。但是如前所述,我认为更新图像文字的策略并不是很重要,
//所以我并没有去实施这个更加全面的方案,而是简单的每次验证后均更新图像文字。
// -- gugu2000cec@msn.com 13:35 2006-7-21

#endregion 同时更新图像文字的理由

if (this.Page.IsPostBack)
{
this.validated_ = true;
switch (this.Manager.Validate(this.token_, this.User_Type))
{
case Gugu.CheckBotComponent.ValidatedState.Pass:
return true;
case Gugu.CheckBotComponent.ValidatedState.Fail:
return false;
default:
this.validated_time_out = true;
return false;
}
}
else
return false;
}

#endregion
}

#endregion CheckBot
}

namespace Gugu.CheckBotComponent
{
#region CheckBotDesigner

public sealed class CheckBotDesigner : System.Web.UI.Design.ControlDesigner
{
//头一个大问题:我如何在 Designer 中知道改变了 checkBot 中的属性呢?

//Gugu.CheckBot Check_Bot
//{
// //get { return (Gugu.CheckBot)this.Component; this.getd}
// get { return (Gugu.CheckBot)this.Component; }
//}

public override string GetDesignTimeHtml()
{
System.IO.MemoryStream ms = new System.IO.MemoryStream();
System.Text.StringBuilder sb = new StringBuilder();
System.IO.StringWriter sw = new System.IO.StringWriter(sb);
System.Web.UI.HtmlTextWriter w = new System.Web.UI.HtmlTextWriter(sw);
System.Random r = new Random();
w.Write(r.Next(10000, 99999).ToString());
w.Flush();
w.Close();
return sb.ToString();
}

protected override string GetEmptyDesignTimeHtml()
{
//return "<b>Emputy</b>";
return base.GetEmptyDesignTimeHtml();
}


protected override string GetErrorDesignTimeHtml(Exception e)
{
//return "<b>Error</b>";
return base.GetErrorDesignTimeHtml(e);
}
}

#endregion CheckBotDesigner

#region CheckBotException

//(此话过时)暴露出来的只有2个地方,一个是 ImageUrl,另一个是 ViewState,使用 ImageUrl 攻击很容易,改改字符串就行啦,我可以理解,而攻击 ViewStata 我不了解,所以我也没有预想可能的异常。
public class CheckBotException : System.Web.HttpException
{
public CheckBotException() { }
public CheckBotException(string message) : base(message) { }
public CheckBotException(string message, System.Exception innerException):base(message,innerException ) { }
}

#endregion CheckBotException

#region EventHandler

public delegate void CheckBotDraTextEventHandler(object sender, CheckBotDrawTextEventArgs e);

public class CheckBotDrawTextEventArgs : Gugu.CheckBotComponent.CheckBotDrawTextArgs
{
internal CheckBotDrawTextEventArgs
(string imageText, System.Drawing.Rectangle rect, System.Drawing.Graphics graphics, System.Drawing.Color backColor, System.Drawing.Color textColor, object expara)
: base(imageText, rect, graphics, backColor, textColor, expara) { }
}

#endregion

#region Interface

#region ICheckBotGenerator

/// <summary>
/// 生产随机的验证码字符串
/// </summary>
public interface ICheckBotGenerator
{
/// <summary>
/// 生产随机的验证码字符串,将在验证码的图像中显示
/// </summary>
/// <returns>随机的验证码字符串</returns>
string GeneratImageText(int expectStringLength);
}

#endregion IGenerator

#region ICheckBotUrlCode

/// <summary>
/// 通过此接口加密Url并传递应如何呈现证码图像的有关参数。
/// </summary>
public interface ICheckBotUrlCode
{
/// <summary>
/// 将应如何呈现证码图像的有关参数转换为加密Url。,
/// </summary>
/// <param name="para">包含需要加密的相应信息</param>
/// <returns>有效的querystring,不应该含有ASCII以外的字符和特殊字符(比如&)。</returns>
string Encode(ImageUrlCodesArgs p);
//ok,结合 ISecretDrawer部分,我发现:验证图片长宽的部分一定要放在这里完成,传到下一步时,长宽必须是合法的,外部攻击的防范应当在这里完成,
//处理可能受到攻击的方法就是抛个异常。
/// <summary>
/// 将为加密Url转换呈现证码图像的相关参数,此处必须处理有可能出现的外部注入攻击,凡是出现预想之外的情况就应该抛出 CheckBotException 异常。
/// </summary>
/// <param name="imageUrl">加密的Url。</param>
/// <returns>呈现证码图像的相关参数。</returns>
ImageUrlCodesArgs Decode(string imageUrl);
}

/// <summary>
/// 作为 IImageUrlCodes 中 Encode() 的参数和 Decode() 的返回值
/// </summary>
public struct ImageUrlCodesArgs
{
/// <summary>
/// 验证码在页面间传递中保存的标示。
/// </summary>
public object Identification;

/// <summary>
/// 验证码图像的宽度。
/// </summary>
public int ImageWidth;

/// <summary>
/// 验证码图像的高度。
/// </summary>
public int ImageHeight;

/// <summary>
/// 验证码图像的期望背景色。
/// </summary>
public System.Drawing.Color ExpectTextBackColor;

/// <summary>
/// 验证码图像文字的期望颜色。
/// </summary>
public System.Drawing.Color ExpectTextColor;

/// <summary>
/// 扩展的其他参数。如没有想传递的扩展参数,应赋值为 null.
/// </summary>
public object ExPara;
}

#endregion IImageUrlCodes

#region ICheckBotDrawer

/// <summary>
/// 提供画验证吗图像的接口
/// </summary>
public interface ICheckBotDrawer
{
//这里有个思考,图片是个很占内存的东西,有没有个对象池在这里用一下啊
//出错画图也不宜公开,最多在部署时可以指定出错图片大小。
/// <summary>
/// 画验证吗图像
/// </summary>
void DrawText(CheckBotDrawTextArgs args);
}

#region public class CheckBotDrawArgs

/// <summary>
/// 用于 IDrawer 接口的 Draw() 方法传递参数,只允许内部实例化。
/// </summary>
public class CheckBotDrawTextArgs
{
//只允许内部实例化。
internal CheckBotDrawTextArgs(string imageText,System.Drawing.Rectangle rect,System.Drawing.Graphics graphics,System.Drawing.Color backColor,System.Drawing.Color textColor,object expara)
{
this.text_ = imageText;
this.rectangle_ = rect;
this.g_ = graphics;
this.expect_image_back_color = backColor;
this.expect_text_color = textColor;
this.other_image_args = expara;
}

private System.Drawing.Graphics g_;

/// <summary>
/// 绘图表面。
/// </summary>
public System.Drawing.Graphics Graphics
{
get { return this.g_; }
}

private string text_;

/// <summary>
/// 需要绘制的字符串。
/// </summary>
public string Text
{
get { return this.text_; }
}

private System.Drawing.Rectangle rectangle_;

/// <summary>
/// 绘图表面的长方型。
/// </summary>
public System.Drawing.Rectangle Rectangle
{
get { return rectangle_; }
}

private System.Drawing.Color expect_image_back_color = System.Drawing.Color.White;

/// <summary>
/// 期望的验证码图片背景色,默认为白色。
/// </summary>
public System.Drawing.Color ExpectImageBackColor
{
get { return expect_image_back_color; }
set { expect_image_back_color = value; }
}

private System.Drawing.Color expect_text_color = System.Drawing.Color.Black;

/// <summary>
/// 期望的验证码图片中的文字色彩,默认为黑色。
/// </summary>
public System.Drawing.Color ExpectTextColor
{
get { return expect_text_color; }
set { expect_text_color = value; }
}

private object other_image_args;

/// <summary>
/// 可扩展参数。
/// </summary>
public object OtherImageArgs
{
get { return other_image_args; }
set { other_image_args = value; }
}
}

#endregion

#endregion IDrawer

#region IChectBotManager

#region public interface IChectBotManager

/// <summary>
/// 管理验证码密文
/// </summary>
public interface IChectBotManager
{
//描述:《达芬奇的密码》中的瑞士银行的描写和适合我的这个接口。首先是去瑞士银行买一个私人保险柜,并设置保险柜的有效期限,放入自己的秘密,瑞士银行会给你一把钥匙,
//在有效期限内,任何人拿着这把钥匙去申请一个更长的有效期,最后在有效期内,任何人拿着这把钥匙可以去查看存放的秘密(这里是ImageText接口,不过这里是和例子最没可比性的地方,
//ImageText是返回一个秘密,但是这个秘密可以不是最终的秘密,可以只是一个相关的东西,但大多数时间是最终的秘密),最终,还有一个验证,用户输入口令,还拿着钥匙,对了则得到秘密,
//错了则保险柜销毁(是否销毁也没定)。

/// <summary>
/// 保存将在图片中显示的文字,并返回一个唯一标示
/// </summary>
/// <param name="Secret">将在图片中显示的文字</param>
/// <returns>token</returns>
object SaveImageText(string imageText, System.TimeSpan expectantPeriod);

/// <summary>
/// 通过标示字符串查询对应图片应显示的文字
/// </summary>
/// <param name="token">标示字符串</param>
/// <returns>图片显示的文字</returns>
string PeekImageText(object token);

/// <summary>
/// 验证用户输入是否正确
/// </summary>
/// <param name="identification">标示字符串</param>
/// <param name="userInput">用户输入的字符串</param>
/// <returns>是否验证通过</returns>
Gugu.CheckBotComponent.ValidatedState Validate(object identification, string userInput);

/// <summary>
/// 申请新的验证码生命周期。
/// </summary>
/// <param name="expectantPeriod">返回 null 表示更新失败,验证码则重建。</param>
object UpdatePeriod(object token,System.TimeSpan expectantPeriod);

/// <summary>
/// 清除保存验证码字符所占用的资源
/// </summary>
/// <param name="identification">唯一标示</param>
void DeleteImageText(object token);
}

#endregion public interface IChectBotManager

#region public enum ValidatedState

/// <summary>
/// 验证码验证结果
/// </summary>
public enum ValidatedState
{
/// <summary>
/// 验证成功
/// </summary>
Pass,

/// <summary>
/// 验证失败
/// </summary>
Fail,

/// <summary>
/// 验证码超时
/// </summary>
TimeOut
}

#endregion public enum ValidatedState

#endregion IChectBotManager

#region ICheckBotFactory

public interface ICheckBotFactory
{
Gugu.CheckBotComponent.ICheckBotGenerator GetCheckBotGenerator();
Gugu.CheckBotComponent.ICheckBotUrlCode GetCheckBotImageUrlCodes();
Gugu.CheckBotComponent.ICheckBotDrawer GetCheckBotDrawer();
Gugu.CheckBotComponent.IChectBotManager GetCheckBotManager();
}

#endregion

#endregion Interface

#region CheckBotGenerator : ICheckBotGenerator

public class CheckBotGenerator : ICheckBotGenerator
{
static System.Random Random_ = new Random();

#region IGenerator Members

public string GeneratImageText(int length)
{
string str = "";
while (length >= 4)
{
str += CheckBotGenerator.Random_.Next(1000, 9999).ToString();
length -= 4;
}
switch (length)
{
case 3:
return str += CheckBotGenerator.Random_.Next(100, 999).ToString();
case 2:
return str += CheckBotGenerator.Random_.Next(10, 99).ToString();
case 1:
return str += CheckBotGenerator.Random_.Next(1, 9).ToString();
default :
return str;
}
}

#endregion
}

#endregion i_defaultGenerator : IGenerator

#region CheckBotUrlCodes : ICheckBotUrlCode

public class CheckBotUrlCodes : ICheckBotUrlCode
{
//需要改进

#region IImageUrlCodes Members

public string Encode(ImageUrlCodesArgs p)
{
System.Runtime.Serialization.IFormatter f = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
System.IO.MemoryStream ms = new System.IO.MemoryStream();
f.Serialize(ms, p.ImageHeight);
f.Serialize(ms,p.ImageWidth);
f.Serialize(ms,p.ExpectTextBackColor.ToArgb());
f.Serialize(ms,p.ExpectTextColor.ToArgb());
f.Serialize(ms, p.Identification);
//f.Serialize(ms, p.ExPara);
byte[] b = ms.ToArray();
ms.Close();
return Convert.ToBase64String(b).Replace('+', '?');
}

public ImageUrlCodesArgs Decode(string imageUrl)
{
System.Runtime.Serialization.IFormatter f = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
byte[] b = Convert.FromBase64String(imageUrl.Replace('?', '+'));
System.IO.MemoryStream ms = new System.IO.MemoryStream(b);
Gugu.CheckBotComponent.ImageUrlCodesArgs p = new ImageUrlCodesArgs();
p.ImageHeight = (int)f.Deserialize(ms);
p.ImageWidth = (int)f.Deserialize(ms);
p.ExpectTextBackColor = System.Drawing.Color.FromArgb((int)f.Deserialize(ms));
p.ExpectTextColor = System.Drawing.Color.FromArgb((int)f.Deserialize(ms));
p.Identification = f.Deserialize(ms);
//p.ExPara = f.Deserialize(ms);
return p;
}

#endregion
}

#endregion public class CheckBotImageUrlCodes : ICheckBotImageUrlCodes

#region CheckBotDrawer :ICheckBotDrawer

public class CheckBotDrawer :ICheckBotDrawer
{
private static System.Random random_ = new Random();

public void DrawText(CheckBotDrawTextArgs a)
{
int l = a.Text.Length;
if (l == 0)
l = 1;
int fs = (int)(a.Rectangle.Height * 4 / 4) > (int)(a.Rectangle.Width / l) ? (int)(a.Rectangle.Width / l) : (int)(a.Rectangle.Height * 4 / 4);
if (fs < 8)
throw new Gugu.CheckBotComponent.CheckBotException("图片无法容下验证码文字。");
System.Drawing.Graphics g = a.Graphics;
g.Clear(a.ExpectImageBackColor);
System.Drawing.Color c = System.Drawing.Color.FromArgb(a.ExpectImageBackColor.ToArgb() * 3 / 5 + a.ExpectTextColor.ToArgb() * 2 / 5);
System.Drawing.Pen p = new System.Drawing.Pen(c);
for (int i = 0; i < 25; i++)
{
int x1 = Gugu.CheckBotComponent.CheckBotDrawer.random_.Next(a.Rectangle.Width);
int x2 = Gugu.CheckBotComponent.CheckBotDrawer.random_.Next(a.Rectangle.Width);
int y1 = Gugu.CheckBotComponent.CheckBotDrawer.random_.Next(a.Rectangle.Height);
int y2 = Gugu.CheckBotComponent.CheckBotDrawer.random_.Next(a.Rectangle.Height);
g.DrawLine(p, x1, y1, x2, y2);
}
System.Drawing.Font font = new System.Drawing.Font("Arial", fs+2, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic));
System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(a.Rectangle, a.ExpectTextColor,
//a.ExpectImageBackColor, 1.2f, true);
System.Drawing.Color.FromArgb(a.ExpectTextColor .ToArgb()*3/5+a.ExpectImageBackColor .ToArgb()*2/5), 2f, true);
g.DrawString(a.Text, font, brush, a.Rectangle);
}
}

#endregion public class CheckBotDrawer :ICheckBotDrawer

#region CheckBotManager : IChectBotManager

public class CheckBotManager : IChectBotManager
{
//有待改进

//默认实现的关键是我不会把密文的信息保存在服务器端,而是将密文的相关信息全部保存在客户端,从而不占用服务器端内存空间

//验证码过期策略:本密文管理策略会忽略 CheckBot 的 ExpectantPeriod 属性,因为所有密文的相关信息均保存在客户端,同样,如果也要把 ExpectantPeriod 保存到客户端,
//但是无法杜绝客户端被篡改,如果客户端被篡改,并修改了 ExpectantPeriod,从而可以出现同一个密文永不过期的情况,如同时此密文被破解,从而验证码完全失效。
//本策略将使验证码分批过期,每10分钟过期1批,从而就算有密文被破解,也只能最多持续20分钟,之后必须破解另一个密文。
//本策略的验证码只使用这一种过期策略,而不会针对每个验证码使用不同的策略,原因是分离的验证码的 ExpectantPeriod 没有保存空间。

//批地标示 使用一个 unCheck{} 循环的 uint
//没1批的加密的 IV和KEy 必须更换。这样就 牵扯到一个 异步 的 托管的问题啦。

//单 key 双 IV。

//格式:前8个字节为时间间隔的变量的 long.仅接着为 text,第3部分为 iv,暂时用 object[] oo[1], oo[0]=1+2;oo[1]=3,来组织

static CheckBotManager()
{
System.Security.Cryptography.SymmetricAlgorithm s = System.Security.Cryptography.SymmetricAlgorithm.Create();
s.GenerateKey();
CheckBotManager.Key = s.Key;
s.GenerateIV();
CheckBotManager.IV = s.IV;
s.Clear();
}

static readonly byte[] Key;
static readonly byte[] IV;

#region IChectBotManager Members

public object UpdatePeriod(object identification, TimeSpan expectantPeriod)
{
return null;//
}

public void DeleteImageText(object identification)
{
//因为数据保存在客户端,所以不用做任何清理。
}

public object SaveImageText(string imageText, System.TimeSpan expectantPeriod)
{
long l = System.DateTime.Now.Add(expectantPeriod).ToBinary();
byte[] b = new byte[System.Text.Encoding.Default.GetByteCount(imageText) + 8];
b[0] = Convert.ToByte(l & 0xff);
int i = 1;
do
{
b[i++] = Convert.ToByte(l & 0xff);
l = l >> 8;
} while (i != 8);
System.Text.Encoding.Default.GetBytes(imageText, 0, imageText.Length, b, 8);
System.IO.MemoryStream ms = new System.IO.MemoryStream(b);
System.Security.Cryptography.SymmetricAlgorithm s = System.Security.Cryptography.SymmetricAlgorithm.Create();
s.GenerateIV();
byte[] iv = s.IV;
System.Security.Cryptography.ICryptoTransform encryptor = s.CreateEncryptor(CheckBotManager.Key, iv);
System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, encryptor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(b, 0, b.Length);
encryptor.Dispose();
ms = new System.IO.MemoryStream(iv);
encryptor = s.CreateEncryptor(CheckBotManager.Key, CheckBotManager.IV);
cs = new System.Security.Cryptography.CryptoStream(ms, encryptor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(iv, 0, iv.Length);
encryptor.Dispose();
s.Clear();
object[] o = new object[2];
o[0] = b;
o[1] = iv;
return o;
}

public Gugu.CheckBotComponent.ValidatedState Validate(object identification, string userInput)
{
try
{
object[] o = (object[])(identification);
byte[] b = o[0] as byte[];
byte[] iv = o[1] as byte[];
System.Security.Cryptography.SymmetricAlgorithm s = System.Security.Cryptography.SymmetricAlgorithm.Create();
System.IO.MemoryStream ms = new System.IO.MemoryStream(iv);
System.Security.Cryptography.ICryptoTransform decrytor = s.CreateDecryptor(CheckBotManager.Key, CheckBotManager.IV);
System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, decrytor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(iv, 0, iv.Length);
decrytor.Dispose();
ms = new System.IO.MemoryStream(b);
decrytor = s.CreateDecryptor(CheckBotManager.Key, iv);
cs = new System.Security.Cryptography.CryptoStream(ms, decrytor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(b, 0, b.Length);
decrytor.Dispose();
s.Clear();

long l = b[7];
int i = 6;
do
{
l = l << 8;
l += b[i--];
} while (i != -1);
System.DateTime d = System.DateTime.FromBinary(l);
if (d > System.DateTime.Now)//超时
return Gugu.CheckBotComponent.ValidatedState.TimeOut;

//CheckBot.test = System.Text.Encoding.Default.GetString(b, 8, b.Length - 8);
if (System.Text.Encoding.Default.GetString(b, 8, b.Length - 8).Trim() == userInput.Trim())
return Gugu.CheckBotComponent.ValidatedState.Pass;
return Gugu.CheckBotComponent.ValidatedState.Fail;
}
catch { return Gugu.CheckBotComponent.ValidatedState.Fail; }
}

public string PeekImageText(object identification)
{
object[] o = (object[])(identification);
byte[] b = o[0] as byte[];
byte[] iv = o[1] as byte[];
System.Security.Cryptography.SymmetricAlgorithm s = System.Security.Cryptography.SymmetricAlgorithm.Create();
System.IO.MemoryStream ms = new System.IO.MemoryStream(iv);
System.Security.Cryptography.ICryptoTransform decrytor = s.CreateDecryptor(CheckBotManager.Key, CheckBotManager.IV);
System.Security.Cryptography.CryptoStream cs = new System.Security.Cryptography.CryptoStream(ms, decrytor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(iv, 0, iv.Length);
decrytor.Dispose();
ms = new System.IO.MemoryStream(b);
decrytor = s.CreateDecryptor(CheckBotManager.Key, iv);
cs = new System.Security.Cryptography.CryptoStream(ms, decrytor, System.Security.Cryptography.CryptoStreamMode.Write);
cs.Write(b, 0, b.Length);
decrytor.Dispose();
s.Clear();

return System.Text.Encoding.Default.GetString(b, 8, b.Length - 8);
}


#endregion
}

#endregion public class CheckBotManager : IChectBotManager

#region CheckBotFactory : ICheckBotFactory

public class CheckBotFactory : Gugu.CheckBotComponent.ICheckBotFactory
{
#region ICheckBotFactory Members

public ICheckBotGenerator GetCheckBotGenerator()
{
return new Gugu.CheckBotComponent.CheckBotGenerator();
}

public ICheckBotUrlCode GetCheckBotImageUrlCodes()
{
return new Gugu.CheckBotComponent.CheckBotUrlCodes();
}

public ICheckBotDrawer GetCheckBotDrawer()
{
return new Gugu.CheckBotComponent.CheckBotDrawer();
}

public IChectBotManager GetCheckBotManager()
{
return new Gugu.CheckBotComponent.CheckBotManager();
}

#endregion
}

#endregion

#region CheckBotUserTypeControlConvert

public class CheckBotUserTypeControlConvert : System.Web.UI.WebControls.ControlIDConverter
{
protected override bool FilterControl(System.Web.UI.Control control)
{
System.Type controltype = control.GetType();
if (controltype == typeof(System.Web.UI.WebControls.TextBox) || controltype == typeof(System.Web.UI.HtmlControls.HtmlInputText))
return true;
if (controltype == typeof(System.Web.UI.HtmlControls.HtmlInputPassword) || controltype == typeof(System.Web.UI.HtmlControls.HtmlTextArea))
return true;
return false;
}
}

#endregion public class CheckBotUserTypeControlConvert : System.Web.UI.WebControls.ControlIDConverter

#region CheckBotManageerSaveAtServer: IChectBotManager

public class CheckBotManageerSaveAtServer:Gugu.CheckBotComponent.IChectBotManager
{
private struct mystrc
{
public string text;
public System.DateTime time;
}

private static int i_ = 0;
public const string GUID = "fdcd854648e1478f808046474654c7c5";

private System.Collections.Generic.Dictionary<int, mystrc> Dictionary_
{
get
{
System.Collections.Generic.Dictionary<int, mystrc> d;
if (System.Web.HttpContext.Current.Cache[CheckBotManageerSaveAtServer.GUID] == null)
{
d = new System.Collections.Generic.Dictionary<int, mystrc>();
System.Web.HttpContext.Current.Cache[CheckBotManageerSaveAtServer.GUID] = d;
}
else
d = (System.Collections.Generic.Dictionary<int, mystrc>)System.Web.HttpContext.Current.Cache[CheckBotManageerSaveAtServer.GUID];
return d;
}
}


#region IChectBotManager Members

public object SaveImageText(string imageText, TimeSpan expectantPeriod)
{
int i;
unchecked { i = CheckBotManageerSaveAtServer.i_++; }
mystrc s = new mystrc();
s.text = imageText;
s.time = System.DateTime.Now + expectantPeriod;
this.Dictionary_.Add(i, s);
return i;
}

public string PeekImageText(object identification)
{
if (this.Dictionary_.ContainsKey((int)identification))
{
mystrc s = this.Dictionary_[(int)identification];
return s.text;
}
return null;
}

public ValidatedState Validate(object identification, string userInput)
{
if (this.Dictionary_.ContainsKey((int)identification))
{
mystrc s = this.Dictionary_[(int)identification];
if (System.DateTime.Now > s.time)
return ValidatedState.TimeOut;
if (s.text.Trim() == userInput.Trim())
return ValidatedState.Pass;
}
return Gugu.CheckBotComponent.ValidatedState.Fail;
}

public object UpdatePeriod(object identification, TimeSpan expectantPeriod)
{
if (this.Dictionary_.ContainsKey((int)identification))
{
mystrc s = this.Dictionary_[(int)identification];
if (System.DateTime.Now > s.time)
{
this.Dictionary_.Remove((int)identification);
return null;
}
else
return identification;
}
return null;
}

public void DeleteImageText(object identification)
{
if (this.Dictionary_.ContainsKey((int)identification))
this.Dictionary_.Remove((int)identification);
}

#endregion
}

#endregion
}

摘自微软中文技术社区 » 专项讨论 » ASP .NET 2005 » [原创]最容易使用的验证码控件(附代码)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
功能介绍:  为网页编程人员提供能够自动生成验证码图像并验证网页访问者输入的Web控件,具体功能如下:1. 提供简单易用的设计时所见即所得的可视化设置方式,并且支持设计时验证码图像效果与特效属性设置的实时联动体现,极大地提高程序员的工作效率;2. 对验证码图像的包括残影、打散、风化、旋转、斜变等多种图形学图像处理特效以及对各种特效的三个程度等级的灵活控制能力,让程序员在轻松应对基于OCR的恶意识别攻击的情况下,还能有充分的余地考虑验证码图像外观的美化问题;3. 提供灵活的中文诗词、中文单字、英文大小写字母、阿拉伯数字等丰富的验证码生成字源混合控制以及对于字数和字体的自定义控制,让程序员在面对不同客户以及网页访问者的特殊要求的同时仍能从容兼顾良好的用户体验;4. 保留对抗OCR的经典的点、线干扰特效以及三个程度等级的灵活控制,并采用色调匹配技术在保证原有的OCR对抗效果的同时加入了更加美观的彩色点、线效果,留给程序员更多的选择;5. 无刷新页面验证。更好地适用于需要填写大量信息的页面验证,有效地避免由于因突发性网络原因导致的验证码图像的无法下载,而使网页用户必须刷新页面而重填其它信息的窘境。运行环境:1. Microsoft Windows XP Professional2. Microsoft Visual Studio 20053. Microsoft .NET Framework V2.04. Microsoft Internet Information Services (IIS) V5.1(注意在子安装选项中选中FrontPage 2000服务器扩展)常见问答:1. 如何将本控件添加到Microsoft Visual Studio 2005的IDE中?a) 在Microsoft Visual Studio 2005中打开源代码的工程文件,重新编译,生成vcg.dll;b) 在工具箱(Toolbox)面板上单击右键,选择Choose Items…菜单,在弹出来的Choose ToolBox Items属性面板中,点击Browse…找到并选定vcg.dll控件。该控件即被添加到工具箱;2. 如何使用控件?a) 确认需要添加本控件的网页为aspx页面。在IIS管理器中将网站属性的ASP.NET version选中为2.0,并在网站虚拟目录属性的“安全”选项卡中添加ASP.NET用户,赋予写入权限;b) 在Microsoft Visual Studio 2005中打开相应的网站,并打开相应的aspx页面文件(在本文中以Default.aspx为例),进入Design编辑模式;c) 从工具箱将之前添加的vcg控件拖到Default.aspx页面上。此时页面上的拖放位置应该出现一个带有文字内容的图片框,说明vcg控件已经被正确添加到页面;d) 现在可以像修改其它标准控件一样通过鼠标对控件进行拖动、缩放等修改,还可以在属性栏对控件进行进一步的细节设置。主要包括针对验证码图像的特效控制和针对验证码本身的文字控制两个方面。具体内容请参考本控件使用手册;e) 在Default.aspx中新增Web控件Button:btnValidate,作为验证促发;在Default.aspx中新增Web控件TextBox:textCode,作为验证码输入;在Default.aspx中新增Web控件Lable:lblMessage,作为验证结果输出;f) 在Default.aspx.cs中添加验证函数:protected void btnValidate_Click(object sender, EventArgs e){ string code = “”; try { //真正的验证码存储在Session[“Code”]中,需要的只是对验证码输入和该值进行比较。 //(如果属性栏中“文字控制”属性卡下的“SessionCode”默认属性值“Code”改变, //则在代码中的Session[”Code”]中引号内的值也必须手工修改成与属性值一致的字符串。) code = Session[”Code”].ToString(); } catch (Exception ex

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值