验证码图像
使用验证码图像防止自动表单提交。
引言
CAPTCHA 是“完全自动区分计算机和人类的公共图灵测试” (completely automated public Turing test to tell computers and humans apart) 的缩写。其含义是,通过某种生成的测试来区分人类和机器的程序。大多数人可以轻松通过的测试,但计算机程序却无法通过。
您可能在注册在线电子邮件或论坛账户时遇到过此类测试。表单可能包含一个扭曲的文本图像,如上所示,您需要将其输入文本字段。
其目的是防止垃圾邮件发送者使用网络机器人自动提交表单数据,以创建电子邮件账户(用于发送垃圾邮件)或提交包含垃圾邮件信息的反馈评论或留言簿条目。图像中的文本通常被扭曲以防止使用 OCR(光学字符识别)软件来绕过该过程。Hotmail、PayPal、Yahoo 和许多博客网站都采用了这项技术。
本文演示了如何创建此类图像并在 ASP.NET Web 窗体中使用它。
背景
您可以在 The CAPTCHA Project 上找到更多关于 CAPTCHA 的信息,并阅读 Scientific American 上题为 Baffling the Bots 的文章,了解其在阻止销售药品、色情内容和传销计划的传播者方面的应用。
然而,在使用此技术之前,您应该考虑它将如何影响您的网站对盲人和其他视障访客的可访问性。PayPal 在其注册表单上尝试通过包含一个指向音频文件的链接来解决此问题,音频文件中会用语音拼出图像中的文本。
此处提供的代码仅生成图像。但是,如果您有生成音频文件的代码,则可以轻松地集成它。
Using the Code
源代码 zip 文件包含一个类和两个 Web 窗体的源代码。要使用它,只需创建一个新的 Web 项目并添加这些项。
文件
- CaptchaImage.cs - 定义了实际创建图像的
CaptchaImage
对象 - Default.aspx, Default.aspx.cs - 示例 Web 窗体
- JpegImage.aspx, JpegImage.aspx.cs - 一个旨在输出 JPEG 图像而非 HTML 的 Web 窗体
让我们来看看每个组件及其用途。
CaptchaImage.cs
CaptchaImage
对象根据要显示的文本、图像尺寸以及可选的字体参数来创建图像。
代码的核心是 GenerateImage()
方法,如下所示,它会创建指定宽度和高度的位图图像。此方法从 CaptchaImage
构造函数调用,因此一旦创建对象的新实例,图像就会准备就绪。
要创建图像,我们首先使用斜纹笔刷填充背景(图像的外观越“脏”,OCR 程序读取它的难度就越大)。
为了使文本适合图像,我们从基于图像高度的初始字体大小开始,并使用 Graphics.MeasureString()
方法来查找绘制文本的最终尺寸。如果文本超出了图像尺寸,我们会减小字体大小并反复测试,直到找到合适的字体大小。
// ====================================================================
// Creates the bitmap image.
// ====================================================================
private void GenerateImage()
{
// Create a new 32-bit bitmap image.
Bitmap bitmap = new Bitmap(
this.width,
this.height,
PixelFormat.Format32bppArgb);
// Create a graphics object for drawing.
Graphics g = Graphics.FromImage(bitmap);
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle rect = new Rectangle(0, 0, this.width, this.height);
// Fill in the background.
HatchBrush hatchBrush = new HatchBrush(
HatchStyle.SmallConfetti,
Color.LightGray,
Color.White);
g.FillRectangle(hatchBrush, rect);
// Set up the text font.
SizeF size;
float fontSize = rect.Height + 1;
Font font;
// Adjust the font size until the text fits within the image.
do
{
fontSize--;
font = new Font(
this.familyName,
fontSize,
FontStyle.Bold);
size = g.MeasureString(this.text, font);
} while (size.Width > rect.Width);
// Set up the text format.
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
// Create a path using the text and warp it randomly.
GraphicsPath path = new GraphicsPath();
path.AddString(
this.text,
font.FontFamily,
(int) font.Style,
font.Size, rect,
format);
float v = 4F;
PointF[] points =
{
new PointF(
this.random.Next(rect.Width) / v,
this.random.Next(rect.Height) / v),
new PointF(
rect.Width - this.random.Next(rect.Width) / v,
this.random.Next(rect.Height) / v),
new PointF(
this.random.Next(rect.Width) / v,
rect.Height - this.random.Next(rect.Height) / v),
new PointF(
rect.Width - this.random.Next(rect.Width) / v,
rect.Height - this.random.Next(rect.Height) / v)
};
Matrix matrix = new Matrix();
matrix.Translate(0F, 0F);
path.Warp(points, rect, matrix, WarpMode.Perspective, 0F);
// Draw the text.
hatchBrush = new HatchBrush(
HatchStyle.LargeConfetti,
Color.LightGray,
Color.DarkGray);
g.FillPath(hatchBrush, path);
// Add some random noise.
int m = Math.Max(rect.Width, rect.Height);
for (int i = 0; i < (int) (rect.Width * rect.Height / 30F); i++)
{
int x = this.random.Next(rect.Width);
int y = this.random.Next(rect.Height);
int w = this.random.Next(m / 50);
int h = this.random.Next(m / 50);
g.FillEllipse(hatchBrush, x, y, w, h);
}
// Clean up.
font.Dispose();
hatchBrush.Dispose();
g.Dispose();
// Set the image.
this.image = bitmap;
}
设置好字体后,我们定义一个 GraphicsPath()
,它本质上是将文本转换为一组线和曲线。然后可以使用 GraphicsPath.Warp()
方法和一些随机生成的值来扭曲它。效果类似于用两相对角的边抓住一张硬纸板,然后稍微扭一下。生成的路径会绘制到图像上,同样使用斜纹笔刷,使其具有“脏”的外观。
为了完成扭曲,会在图像上随机绘制小的污点。您可以尝试其他方法来阻止 OCR,但请记住,它仍然应该对人类(其中一些人可能有视力障碍)来说是可读的。
Default.aspx
这是一个非常简单的示例 Web 窗体,仅包含几个基本元素,即图像的 <IMG>
标签、一个文本框和一个“提交”按钮。
<form id="Default" method="post" runat="server">
<img src="JpegImage.aspx"><br>
<p>
<strong>Enter the code shown above:</strong><br>
<asp:TextBox id="CodeNumberTextBox" runat="server"></asp:TextBox>
<asp:Button id="SubmitButton" runat="server" Text="Submit">
</asp:Button><br>
</p>
<p>
<em class="notice">
(Note: If you cannot read the numbers in the above<br>
image, reload the page to generate a new one.)</em>
</p>
<p><asp:Label id="MessageLabel" runat="server"></asp:Label></p>
</form>
请注意,<IMG>
标签的 SRC
属性指向 Web 窗体 JpegImage.aspx。
Default.aspx 的代码隐藏部分只是生成一个随机文本 string
用于图像,并在提交表单时验证用户是否输入了此文本。关键是将 text
string
存储在 Session
对象中。
private void Page_Load(object sender, System.EventArgs e)
{
if (!this.IsPostBack)
// Create a random code and store it in the Session object.
this.Session["CaptchaImageText"] = GenerateRandomCode();
else
{
// On a postback, check the user input.
if (this.CodeNumberTextBox.Text ==
this.Session["CaptchaImageText"].ToString())
{
// Display an informational message.
this.MessageLabel.CssClass = "info";
this.MessageLabel.Text = "Correct!";
}
else
{
// Display an error message.
this.MessageLabel.CssClass = "error";
this.MessageLabel.Text = "ERROR: Incorrect, try again.";
// Clear the input and create a new random code.
this.CodeNumberTextBox.Text = "";
this.Session["CaptchaImageText"] = GenerateRandomCode();
}
}
}
将 text
string
存储在 Session
对象中的原因是,以便 JpegImage.aspx 可以访问它。
JpegImage.aspx
对于此 Web 窗体,不需要 HTML(其中包含的内容只是 Visual Studio 在创建文件时生成的默认代码)。代码将生成 JPEG 图像而非 HTML。
在代码隐藏部分,我们首先创建一个 CaptchaImage
对象,使用从 Session
对象中检索的文本。这会为我们创建一个位图图像。
private void Page_Load(object sender, System.EventArgs e)
{
// Create a CAPTCHA image using the text stored in the Session object.
CaptchaImage ci = new CaptchaImage(
this.Session["CaptchaImageText"].ToString(),
200, 50, "Century Schoolbook");
// Change the response headers to output a JPEG image.
this.Response.Clear();
this.Response.ContentType = "image/jpeg";
// Write the image to the response stream in JPEG format.
ci.Image.Save(this.Response.OutputStream, ImageFormat.Jpeg);
// Dispose of the CAPTCHA image object.
ci.Dispose();
}
然后,我们修改 HTTP 响应头,将 Content-type
设置为 "image/jpeg
",以便客户端浏览器知道我们正在发送图像。
最后一步是从 CaptchaImage.Image
中检索位图图像,并以 JPEG 格式将其写入 HTTP 响应输出流。幸运的是,Bitmap
对象的 Save()
方法使此操作变得简单。只要相应地设置 Content-type
头,也可以使用任何其他支持的图像格式。
关注点
由于 CaptchaImage
类包含一个 Bitmap
对象,而位图使用非托管资源,因此实现了一个自定义的 Dispose()
方法。这允许在销毁 CaptchaImage
时释放这些非托管资源。
历史
- 2004年1月26日 - 版本 1.0
- 初始版本
- 2004年2月2日 - 版本 1.01
- 小修正,以纠正
CaptchaImage.SetDimensions()
中的参数检查
- 小修正,以纠正