ASP.NET 控件可防止机器人自动注册






3.80/5 (29投票s)
2002年11月8日
6分钟阅读

351993

4143
一组控件,当放置在网页表单上时,通过强制用户输入图片中显示的安全码来帮助防止注册页面被滥用。
引言
AntiAuto 是一组控件,可用于 ASP.NET Web 表单,以防止“机器人”自动注册。这是最近在 Lounge 中提到的,我认为它将成为 ASP.NET 卓越的应用程序架构的一个很好的例子。
许多网站要求用户注册才能获得完全访问权限。例如,Hotmail.com 要求您输入一些基本信息才能为您提供电子邮件地址。然而,这种服务容易被滥用——通过自动完成注册过程并生成随机用户名等的软件自动生成大量电子邮件地址用于发送垃圾邮件。
为了防止这种滥用,许多网站要求用户输入图片中显示的代码——从而强制用户手动输入注册详情。
目标是开发一组易于部署且能适应现有注册流程的 ASP.NET 控件。
解决方案概览
为了概述项目中的类,这里有一个快速的 UML 类图。
重要的类是 CodeImage
和 CodeImageValidator
。 Util
是一个支持类,提供其他各种部分所需的加密和解密功能。
CodeImage
此类的目的是在网页表单上生成显示图像的 HTML。图像本身由 DrawImage.aspx
文件生成,该文件又使用 PictureGenerator
类。
CodeImageValidator
这是一个派生自 BaseValidator
的类,用于验证关联文本框 (ControlToValidate
) 的内容是否与 CodeImage
控件 (CodeImageControl
) 中绘制的内容匹配。
它们如何协同工作
当页面首次加载时,会生成一个随机数,然后将其作为查询字符串的一部分传递给 DrawImage.aspx
页面。此数字在所有回发期间都保留在 ViewState 中。在回发期间,验证控件 (CodeImageValidator
) 用于确保表单可以成功验证。
实现细节
CodeImage
CodeImage
在 CodeImage.cs
文件中实现。该控件的目的是输出必要的 HTML 代码以绘制图像。例如,如果 DrawImage.aspx
页面要绘制图片,则 HTML 输出将如下所示
<img src="DrawImage.aspx?code=12345">
整个类的代码如下
public class CodeImage : Control
{
private string _key;
private int _digits;
public CodeImage()
{
_key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
_digits = Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["Digits"]);
}
public string Code
{
get
{
return (string)ViewState["SecretCode"];
}
set
{
ViewState["SecretCode"] = value;
}
}
protected override void Render(HtmlTextWriter output)
{
output.Write( String.Format("<img src=\"DrawImage.aspx?code={0}\">",
HttpUtility.UrlEncode(Code)));
}
protected override void OnLoad(System.EventArgs e)
{
if (!this.Page.IsPostBack)
{
StringBuilder sbCode = new StringBuilder(_digits,_digits);
Random R = new Random();
int i;
int MaxLimit = 9;
for(i = 0; i < _digits; i++)
{
sbCode.Append(R.Next(MaxLimit));
}
Code = Util.EncryptString(sbCode.ToString(),_key);
}
}
}
代码非常简单,在构造函数中加载了一些配置设置。这些设置在各种控件和页面中通用,因此使用 web.config
配置文件是最好的方式。
Render 方法被重写以输出 HTML 代码,图片中要显示的安全码来自 Code
属性。由于 HTML 代码对用户可见,因此有必要在将其放入 QueryString
之前对数字进行加密,因此,不是使用 *code=12345*,而是使用 *code=jA89AlxmmA* 等。此加密由 Util
类中的 EncryptString
方法执行。当 PictureGenerator
类被要求渲染图像时,代码会被解密。
同样重要的是要注意,加密后的安全码会首先进行 UrlEncode
处理,以确保在将其放入 QueryString
之前,任何无效字符都能得到安全处理。然后,它们会在另一端(在 DrawImage.aspx
中)进行 UrlDecode
处理。
如果页面是首次加载(即不在回发中),则 OnLoad
事件用于生成一个随机数。一旦生成了安全码,它就会存储在 Code
属性中,该属性又将其存储在 ViewState 中(从而允许内容在回发之间持久化)。
CodeImage
控件将直接放入 ASP.NET Web 表单中,其代码将在本文后面介绍。
CodeImageValidator
该类在 CodeImageValidator.cs
文件中实现,用于检查关联文本框的内容是否与 CodeImage
控件生成的代码匹配。
同样,代码相对简单,因此在讨论之前,这里是整个类的实现
public class CodeImageValidator : BaseValidator
{
TextBox _codeTextBox;
string _codeImageId;
CodeImage _codeImageControl;
string _key;
public CodeImageValidator()
{
_key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
}
protected override bool ControlPropertiesValid()
{
// Should have a text box control to check
Control ctrl = FindControl(ControlToValidate);
Control imageControl = FindControl(_codeImageId);
if ( (null != ctrl) && (null != imageControl) )
{
if ((ctrl is System.Web.UI.WebControls.TextBox) &&
(imageControl is CodeImage))
{
_codeTextBox = (System.Web.UI.WebControls.TextBox) ctrl;
_codeImageControl = (CodeImage) imageControl;
return ( (null != _codeTextBox) && (null != _codeImageControl) );
}
else
return false;
}
else
return false;
}
public string CodeImageControl
{
get
{
return _codeImageId;
}
set
{
_codeImageId = value;
}
}
protected override bool EvaluateIsValid()
{
return (Util.DecryptString(_codeImageControl.Code,_key) ==
_codeTextBox.Text);
}
}
CodeImageValidator
派生自 BaseValidator
,因此它应该像任何其他验证控件一样运行。它需要重写在提交表单时将调用的 EvaluateIsValid
方法。
ControlPropertiesValid
方法用于获取验证期间使用的控件的引用——TextBox
和 CodeImage
控件。这些引用然后保存在 ctrl
和 imageControl
字段中。CodeImageValidator
控件的两个属性用于在 ASPX 页面中设置它们,分别是 ControlToValidate
和 CodeImageControl
。
CodeImage
控件有一个属性 (Code
),其中包含要输入到文本框中的加密安全码。然后,在 EvaluateIsValid
方法中解密该安全码,并将其与文本框的内容进行比较。如果它们匹配,则验证成功,并且该方法可以返回 true。
DrawImage.aspx
DrawImage 页面用于生成发送到浏览器的 JPEG 流,并使用 PictureGenerator
类。DrawImage 页面的代码如下
<%@ Page Language="C#" ContentType="image/jpeg" %>
<script language="C#" runat="server">
void Page_Load (Object sender, EventArgs e)
{
// Draw the output
Etier.AntiAuto.PictureGenerator.OutputPicture( this,
HttpUtility.UrlDecode(Request.QueryString["code"]) );
}
</script>
首先,ContentType
被设置为 image/jpeg(确保浏览器将内容渲染为图像,而不是 HTML 页面或任何其他类型)。调用 OutputPicture
方法,传入当前页面和要渲染的加密安全码的引用。
OutputPicture
方法的代码如下
public static void OutputPicture(System.Web.UI.Page page, string encryptedCode)
{
page.Response.Clear();
string key = System.Configuration.ConfigurationSettings.AppSettings["EncryptionKey"];
int digits = Int32.Parse(System.Configuration.ConfigurationSettings.AppSettings["Digits"]);
Bitmap codeBitmap = new Bitmap((digits*16)+20,26, PixelFormat.Format24bppRgb);
Graphics g = Graphics.FromImage(codeBitmap);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.Clear(Color.Orange);
g.DrawString( Util.DecryptString(encryptedCode,key), new Font("Arial", 16,
FontStyle.Bold ), SystemBrushes.WindowText, new Point(10,2));
codeBitmap.Save( page.Response.OutputStream, ImageFormat.Jpeg);
g.Dispose();
codeBitmap.Dispose();
page.Response.End();
}
该代码基于 Nick Parker 在 CodeProject 上的文章“ASP.NET 中的实时 Web 图形”。它加载配置设置,然后渲染图像,首先解密安全码。安全码取自 DrawImage.aspx 的 QueryString
,并由 CodeImage
控件加密,以防止访问者在渲染的图片之外确定安全码。
如何使用
演示应用程序包含一个即插即用的示例,但对于现有的 ASP.NET Web Form,还需要做一些事情
- 将 AntiAuto 程序集复制到应用程序的 /bin 目录
将 web.config 配置文件复制到应用程序的根目录
将 DrawImage.aspx 复制到包含 Web Form 的同一目录 - 在 web form 顶部添加一个导入命令
<%@ Register TagPrefix="etier" Namespace="Etier.AntiAuto" Assembly="AntiAuto" %>
这会将命名空间映射到标签前缀,这样你就可以以 <prefix:ControlClass 的形式添加控件。 - 向 web form 添加一个
CodeImage
控件
<etier:CodeImage ID="codeImageControl" RunAt="server" />
在运行时,这会生成 <img src="..."> HTML 代码来生成图像。 - 向 web form 添加一个
CodeImageValidator
控件
<etier:CodeImageValidator ID="codeImageValidator" ControlToValidate="codeTextBox" CodeImageControl="codeImageControl" ErrorMessage="Please enter the text in the image" Display="Static" RunAt="server" />
重要的是ControlToValidate
和CodeImageControl
必须设置为您为TextBox
和CodeImage
控件指定的 ID。
结论
这些控件非常简单,展示了 ASP.NET 新架构的辉煌。我希望人们觉得它有用并能改进它。抱歉代码中没有(或很少有)注释,但由于本文涵盖了它们如何协同工作,所以应该相对容易理解。当我重新审视解决方案时,我会把事情整理得更整洁一些,我肯定想做的一件事是制作一个单一的控件,以便部署更加容易。
如果您有任何问题或意见,请在下面留言或给我发电子邮件。