ASP.NET 2.0 下的验证码控件

[简介]

验证码是阻止恶意用户采用自动注册机/发帖机的一个好方法,也许你已经在Google,yahoo等大型网站上见到它的应用了。本文会给你一个这样的控件。

[源代码]

我使用的第一个验证码控件是BrainJar写的CAPTCHA Image article,在此之后,我又读了文章MSDN HIP challenge article,并在我的代码中做了不少 的改动。本文中的代码就是基于MSDN HIP的文章。

[实现方法]

Captcha.ascx 是一个用户控件文件。当加载的时候,调用SetCaptcha()方法。
RandomText 类产生随机文字
RNG 类产生随机数字
CaptchaImage 类产生图像
Encryptor 用于加密和解密
Captcha.ashx 返回图像

下面讨论这些组建:

[用户控件]

用户控件中的主要方法是SetCaptcha(),无论何时,只要你需要改变图像或者加载图片,它会被执行。private
Code:
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;
}



这个方法会使用一个密钥加密文字,在本文代码中这个密钥是在文件中写死的,如果你想动态改变它,可以将其存放在数据库中。这个方法还向Session中保存文字,以在用户输入之后比较。

另外用户控件还有两个属性,用以控制其风格:
Style
Background color


两个事件处理句柄处理成功和失败事件,我们在这些事件上使用委托:

Code:
public delegate void CaptchaEventHandler();



当用户点击提交的时候,btnSubmit_Click() 验证其输入:

Code:
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类产生随机数字。

Code:
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中一个很有用的技术。

Code:
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(46);
        
for (int i = 0; i < lenght; i++)
        {
            index 
= RNG.Next(chars.Length - 1);
            s 
+= chars[index].ToString();
        }
        
return s;
    }
}


CaptchaImage 类

该类是本控件的核心,它得到图像文字、尺寸、背景颜色,然后产生图像。

主要的方法是GenerateImage(),它根据我们提供的信息产生图片。
Code:
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(00this.width, this.height);
    g.SmoothingMode 
= SmoothingMode.AntiAlias;

    
// Fill background
    using (SolidBrush b = new SolidBrush(bc))
    {
        g.FillRectangle(b, rect);
    }



首先,声明Bitmap Graphics 对象,以及一个和Bitmap 对象一样尺寸的Rectangle 。然后使用SolidBrush填充背景。

现在,我们需要设置字体大小,因为,字体是从fonts字体集合中随机选择的。

Code:
// 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(00);
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);
}



下一步使用GraphicsPath 绘制文字。

Code:
GraphicsPath path = new GraphicsPath();
path.AddString(
this.text, font.FontFamily, 
        (
int)font.Style, font.Size, rect, format);



最重要的部分是给文字加颜色及扭曲文字:随机选择0-255之间的数字,然后使用这个RGB值给文字设置颜色。并且,需要检查颜色和背景色是否能够区分。

Code:
// 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(0255);
}
SolidBrush sBrush 
= new SolidBrush(Color.FromArgb(red, green, blue));
g.FillPath(sBrush, path);



最后,扭曲图像。

Code:

// Iterate over every pixel
double distort = random.Next(520* (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 句柄得到需要创建验证码图像的信息,然后创建一个。注意:它接收到的是加密后的文字,并使用密钥解密。

Code:
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,在处理的时候自动加上#。

[总结]

在文章的源代码中,除了控件的源代码,你还可以见到一个使用这个控件的例子。
https://files.cnblogs.com/jueban/QQ(1).rar

posted @ 2009-03-28 11:46  智者生存  阅读(200)  评论(0编辑  收藏  举报