验证码是阻止恶意用户采用自动注册机/发帖机的一个好方法,也许你已经在Google,yahoo等大型网站上见到它的应用了。本文会给你一个这样的控件。
[源代码]
我使用的第一个验证码控件是BrainJar写的CAPTCHA Image article,在此之后,我又读了文章MSDN HIP challenge article,并在我的代码中做了不少 的改动。本文中的代码就是基于MSDN HIP的文章。
[实现方法]
Captcha.ascx 是一个用户控件文件。当加载的时候,调用SetCaptcha()方法。
RandomText 类产生随机文字
RNG 类产生随机数字
CaptchaImage 类产生图像
Encryptor 用于加密和解密
Captcha.ashx 返回图像
下面讨论这些组建:
[用户控件]
用户控件中的主要方法是SetCaptcha(),无论何时,只要你需要改变图像或者加载图片,它会被执行。private
{
// 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
两个事件处理句柄处理成功和失败事件,我们在这些事件上使用委托:
当用户点击提交的时候,btnSubmit_Click() 验证其输入:
{
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类产生随机数字。
{
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 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(),它根据我们提供的信息产生图片。
{
// 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填充背景。
现在,我们需要设置字体大小,因为,字体是从fonts字体集合中随机选择的。
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);
}
下一步使用GraphicsPath 绘制文字。
path.AddString(this.text, font.FontFamily,
(int)font.Style, font.Size, rect, format);
最重要的部分是给文字加颜色及扭曲文字:随机选择0-255之间的数字,然后使用这个RGB值给文字设置颜色。并且,需要检查颜色和背景色是否能够区分。
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);
最后,扭曲图像。
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 句柄得到需要创建验证码图像的信息,然后创建一个。注意:它接收到的是加密后的文字,并使用密钥解密。
{
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