ASP.NET 2 的 CAPTCHA 控件






4.86/5 (52投票s)
一个简单、安全、易用的 CAPTCHA 控件。
注意:此控件的新版本可在 此处 获得。此控件的所有后续更新都将发布到 SourceForge.net 上的项目页面。

引言
CAPTCHA 是“完全自动化的公共图灵测试,以区分计算机和人类”(completely automated public Turing test to tell computers and humans apart)的缩写,它是用于防止计算机程序向 Web 服务器发送自动化请求的最常用技术。这些请求可能是为了元搜索搜索引擎、在登录页面执行字典攻击,或使用邮件服务器发送垃圾邮件。当您在 Google 注册页面检查过多用户名的可用性时,或者在 Yahoo!、PayPal 或其他大型网站上,您可能已经见过 CAPTCHA 图片。
来源
我使用的第一个 CAPTCHA 图片生成器是基于 BrainJar 的 CAPTCHA Image 文章编写的。在那之后,我阅读了 MSDN HIP 挑战文章,并对我的代码进行了许多修改。此控件中使用的代码基于 MSDN HIP 文章,但有些部分并未更改。
工作原理
- Captcha.ascx 是控件文件。加载时,它会调用
SetCaptcha()
方法。此方法使用其他类来完成所有必要的工作。 RandomText
类生成加密安全的随机文本。RNG
类生成加密安全的随机数字。CaptchaImage
类创建图像。Encryptor
类用于加密和解密。- Captcha.ashx 返回图像。
我们将在本文稍后讨论其中的一些内容。
控件
控件代码中的主要方法是 SetCaptcha()
,每次我们需要更改图片或加载图片时都会执行此方法。
private void SetCaptcha()
{
// Set image
string s = RandomText.Generate();
// Encrypt
string ens = Encryptor.Encrypt(s, "srgerg$%^bg",
Convert.FromBase64String("srfjuoxp"));
// Save to session
Session["captcha"] = s.ToLower();
// Set URL
imgCaptcha.ImageUrl = "~/Captcha.ashx?w=305&h=92&c=" +
ens + "&bc=" + color;
}
此方法使用硬编码在此代码中的加密密钥来加密随机文本。为了避免硬编码,您可以将此信息存储在数据库中,并在需要时检索它。此方法还将文本保存到会话中,以便与用户输入进行比较。
为了使控件样式与页面样式匹配,使用了两个属性
- 样式
- 背景颜色
Style 属性设置控件样式,背景颜色设置生成图像的背景颜色。
两个事件处理程序处理 Success
和 Failure
事件。我们为这些处理程序使用 delegate
。
public delegate void CaptchaEventHandler();
当用户提交表单时,btnSubmit_Click()
会验证用户输入。
protected void btnSubmit_Click(object s, EventArgs e)
{
if (Session["captcha"] != null && txtCaptcha.Text.ToLower() ==
Session["captcha"].ToString())
{
if (success != null)
{
success();
}
}
else
{
txtCaptcha.Text = "";
SetCaptcha();
if (failure != null)
{
failure();
}
}
}
RNG 类
RNG
类使用 RNGCryptoServiceProvider
类生成加密安全的随机数字。
public static class RNG
{
private static byte[] randb = new byte[4];
private static RNGCryptoServiceProvider rand
= new RNGCryptoServiceProvider();
public static int Next()
{
rand.GetBytes(randb);
int value = BitConverter.ToInt32(randb, 0);
if (value < 0) value = -value;
return value;
}
public static int Next(int max)
{
// ...
}
public static int Next(int min, int max)
{
// ...
}
}
RandomText 类
为了创建加密安全的随机文本,我们使用 RNG
类从字符数组中随机选择每个字符。这是一种我首次在 CryptoPasswordGenerator 中看到的非常有用的技术。
public static class RandomText
{
public static string Generate()
{
// Generate random text
string s = "";
char[] chars = "abcdefghijklmnopqrstuvw".ToCharArray() +
"xyzABCDEFGHIJKLMNOPQRSTUV".ToCharArray() +
"WXYZ0123456789".ToCharArray();
int index;
int lenght = RNG.Next(4, 6);
for (int i = 0; i < lenght; i++)
{
index = RNG.Next(chars.Length - 1);
s += chars[index].ToString();
}
return s;
}
}
CaptchaImage 类
这是我们控件的核心。它获取图像文本、尺寸和背景颜色,然后生成图像。
主要方法是 GenerateImage()
,它使用我们提供的信息生成图像。
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);
Rectangle rect = new Rectangle(0, 0, this.width, this.height);
g.SmoothingMode = SmoothingMode.AntiAlias;
// Fill background
using (SolidBrush b = new SolidBrush(bc))
{
g.FillRectangle(b, rect);
}
首先,我们声明 Bitmap
和 Graphics
对象,以及一个尺寸与 Bitmap
对象相同的 Rectangle
。然后,使用 SolidBrush
填充背景。
现在,我们需要设置 font
大小以适应图像。font
系列从 fonts
系列集合中随机选择。
// Set up the text font.
int emSize = (int)(this.width * 2 / text.Length);
FontFamily family = fonts[RNG.Next(fonts.Length - 1)];
Font font = new Font(family, emSize);
// Adjust the font size until
// the text fits within the image.
SizeF measured = new SizeF(0, 0);
SizeF workingSize = new SizeF(this.width, this.height);
while (emSize > 2 &&
(measured = g.MeasureString(text, font)).Width
> workingSize.Width || measured.Height
> workingSize.Height)
{
font.Dispose();
font = new Font(family, emSize -= 2);
}
我们通过将图像宽度乘以 2 然后除以文本长度来计算 font
的大小。在大多数情况下效果都很好;例如,当文本长度为 4 且宽度为 8 像素时,font
大小将为 4。但如果文本长度为 1,则 font
大小将为 16。此外,当图像 height
太小时,文本将无法适应图像。当计算出的尺寸小于 2 时,我们可以确定它适合图像,除非图像 height
非常短,我们不予关注。但当它大于 2 时,我们必须确保文本适合图像。我们通过获取所选 font
和大小所需的文本 width
和 height
来做到这一点。如果 width
或 height
不合适,则减小尺寸并反复检查,直到适合为止。
下一步是添加文本。这是使用 GraphicsPath
对象完成的。
GraphicsPath path = new GraphicsPath();
path.AddString(this.text, font.FontFamily,
(int)font.Style, font.Size, rect, format);
最重要的部分是为文本着色和扭曲。我们使用 RGB 代码设置文本颜色,每个代码使用 0 到 255 之间的随机值。然后成功生成随机颜色。现在,我们必须检查颜色是否在背景色可见。这是通过计算文本颜色 R 通道与背景颜色 R 通道之间的差值来完成的。如果小于 20,则重新生成 R 通道值。
// Set font color to a color that is visible within background color
int bcR = Convert.ToInt32(bc.R);
int red = random.Next(256), green = random.Next(256), blue =
random.Next(256);
// This prevents font color from being near the bg color
while (red >= bcR && red - 20 < bcR ||
red < bcR && red + 20 > bcR)
{
red = random.Next(0, 255);
}
SolidBrush sBrush = new SolidBrush(Color.FromArgb(red, green, blue));
g.FillPath(sBrush, path);
最后,我们通过更改像素颜色来扭曲图像。对于每个像素,我们从原始图片(我们不更改的 Bitmap
对象)中选择一个随机像素,并为其设置一个随机像素颜色。由于 distort
是随机的,我们会看到不同的扭曲效果。
// Iterate over every pixel
double distort = random.Next(5, 20) * (random.Next(10) == 1 ? 1 : -1);
// Copy the image so that we're always using the original for
// source color
using (Bitmap copy = (Bitmap)bitmap.Clone())
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
// Adds a simple wave
int newX =
(int)(x + (distort * Math.Sin(Math.PI * y / 84.0)));
int newY =
(int)(y + (distort * Math.Cos(Math.PI * x / 44.0)));
if (newX < 0 || newX >= width)
newX = 0;
if (newY < 0 || newY >= height)
newY = 0;
bitmap.SetPixel(x, y,
copy.GetPixel(newX, newY));
}
}
}
Captcha.ashx
此 HTTP 处理程序获取创建 CAPTCHA
图像所需的信息,并返回一个。请注意,此处理程序接收加密文本并拥有解密它的密钥。
public class Captcha : IHttpHandler
{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "image/jpeg";
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
context.Response.BufferOutput = false;
// Get text
string s = "No Text";
if (context.Request.QueryString["c"] != null &&
context.Request.QueryString["c"] != "")
{
string enc = context.Request.QueryString["c"].ToString();
// space was replaced with + to prevent error
enc = enc.Replace(" ", "+");
try
{
s = Encryptor.Decrypt(enc, "srgerg$%^bg",
Convert.FromBase64String("srfjuoxp"));
}
catch { }
}
// Get dimensions
int w = 120;
int h = 50;
// Width
if (context.Request.QueryString["w"] != null &&
context.Request.QueryString["w"] != "")
{
try
{
w = Convert.ToInt32(context.Request.QueryString["w"]);
}
catch { }
}
// Height
if (context.Request.QueryString["h"] != null &&
context.Request.QueryString["h"] != "")
{
try
{
h = Convert.ToInt32(context.Request.QueryString["h"]);
}
catch { }
}
// Color
Color Bc = Color.White;
if (context.Request.QueryString["bc"] != null &&
context.Request.QueryString["bc"] != "")
{
try
{
string bc = context.Request.QueryString["bc"].
ToString().Insert(0, "#");
Bc = ColorTranslator.FromHtml(bc);
}
catch { }
}
// Generate image
CaptchaImage ci = new CaptchaImage(s, Bc, w, h);
// Return
ci.Image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
// Dispose
ci.Dispose();
}
public bool IsReusable
{
get
{
return true;
}
}
}
关于此类只有两点需要注意
- 由于 URL 中的 '+' 表示空格,因此我们将空格替换为 '+'(对于加密文本)。
- 在 URL 中使用 '#' 会导致问题。我们不发送 '#' 随颜色值一起发送。例如,当颜色为 #ffffff 时,我们发送 ffffff,然后在处理程序中添加 '#'。
摘要
当控件加载时,它会执行 SetCaptcha()
方法。RandomText
类生成随机文本,我们将文本保存到 Session
对象并进行加密。然后,此方法使用尺寸、加密文本和背景颜色信息生成图像 URL。
您可以在源代码中看到使用此控件的示例。