65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET 2 的 CAPTCHA 控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (52投票s)

2006 年 2 月 25 日

LGPL3

5分钟阅读

viewsIcon

368328

downloadIcon

5668

一个简单、安全、易用的 CAPTCHA 控件。

注意:此控件的新版本可在 此处 获得。此控件的所有后续更新都将发布到 SourceForge.net 上的项目页面。

Sample Image - CaptchaNET_2.gif

引言

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 属性设置控件样式,背景颜色设置生成图像的背景颜色。

两个事件处理程序处理 SuccessFailure 事件。我们为这些处理程序使用 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);
    }

首先,我们声明 BitmapGraphics 对象,以及一个尺寸与 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 来做到这一点。如果 widthheight 不合适,则减小尺寸并反复检查,直到适合为止。

下一步是添加文本。这是使用 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;
        }
    }
}

关于此类只有两点需要注意

  1. 由于 URL 中的 '+' 表示空格,因此我们将空格替换为 '+'(对于加密文本)。
  2. 在 URL 中使用 '#' 会导致问题。我们不发送 '#' 随颜色值一起发送。例如,当颜色为 #ffffff 时,我们发送 ffffff,然后在处理程序中添加 '#'。

摘要

当控件加载时,它会执行 SetCaptcha() 方法。RandomText 类生成随机文本,我们将文本保存到 Session 对象并进行加密。然后,此方法使用尺寸、加密文本和背景颜色信息生成图像 URL。

您可以在源代码中看到使用此控件的示例。

© . All rights reserved.