做一个简单的图片验证码生成,避免被 ai 简单识别出文本
- 缘由
- 腾讯云的收费标准
- 网易的收费标准
- 编写一个图片验证码生成
- c# 示例
缘由
在很多场合,我们会对用户进行一个真实性人工验证,避免各种注册机、机器人之类的,对我们的正常工作造成干扰。
比如说,用户注册时,手机真实性验证时,通过发短信验证手机真实性,而通过图形验证码来验证时由真人操作的。
而现在,由于人工智能技术发展的速度太快了,原有的一些简单的图片信息,会被 ai 简单的就能识别出来。
例如这样的一个图片,其构造十分简单,很容易丧失真实人工操作验证的机能。
CSDN 文盲老顾的博客,https://blog.csdn.net/superwfei
老顾的个人社区,https://bbs.csdn.net/forums/bfba6c5031e64c13aa7c60eebe858a5f?category=10003&typeId=3364713
当然,网络上也有 captcha.js 这样的非常棒的人机验证手段。
但是。。。验证码这个东西,肯定不是能纯 js 的,要将真实的验证字符隐藏在服务器上,所以纯 js 版的要 pass 掉。
如果不是纯 js 版的, captcha 需要做一些服务器端支持,安装一下 node.js 之类的。
当然,这些都还好说,关键是,现在 captcha.js 收费了?
腾讯云的收费标准
网易的收费标准
好家伙,网易这个收费真高,咱小平台根本用不上每小时 3500 次验证的这个档次啊,整个平台日活有没有 3500 真人都不知道呢。
算了,还是自己从新写一个简单的图片验证码生成好了。
编写一个图片验证码生成
基于现在 ai 的先进性能,如之前图片那样的小验证码图片肯定不行了,图片太小,无法放置过多的内容,所有内容基本都是有效的,对 ai 的干扰太少了。
所以,第一个点就是,验证码图片需要放大。只有图片放大了之后,我们才能多放一些东西,比如一些干扰性的内容。
然后,对生成的文本进行内容干扰,老顾在这里就先用一个简单的方式来演示一下。
生成多段文字,其中一段是正确的验证码,用颜色进行区分,类似下图:
然后,除了干扰字符外,每个字符都进行一定随机角度的旋转。还可以在服务器上安装上类似华文彩云这样的字体,生成镂空字符。
当然,这是一个简单版本的,后续还可以用其他方式干扰,比如设置,选择第几行的字符,比如选择某个范围内的字符(长方形以内、),甚至可以使用问题,比如九月初九,验证码是重阳节这个样子的。
反正,生成图片验证码,怎么生成,就是身为程序员你自己的问题了。
c# 示例
// 因为是用来生成图片,所以,使用 ashx 一般处理程序即可
namespace xxxxxx
public class Verify_Code : IHttpHandler, IRequiresSessionState
{
private List<int> usedColor = new List<int>();
private List<int> usedSize = new List<int>();
private List<int> usedFamily = new List<int>();
private Color[] colors = new Color[] { Color.Black, Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Brown, Color.Yellow, Color.SkyBlue, Color.White, Color.Purple };
private string colorName = "黑红蓝绿橙棕黄青白紫";
private string[] family = new string[] { "Arial", "Book Antiqua", "Consolas", "黑体", "华文彩云", "微软雅黑", "楷体", "Verdana" };
private Random rnd = new Random();
private int ChoiceColor()
{
int r = rnd.Next(colors.Length);
while (usedColor.Contains(r))
{
r = rnd.Next(colors.Length);
}
usedColor.Add(r);
return r;
}
public void ProcessRequest(HttpContext context)
{
int codeW = 300;
int codeH = 150;
string chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
string[] codes = new string[] { RandomString(chars, rnd.Next(3) + 4), RandomString(chars, rnd.Next(3) + 4), RandomString(chars, rnd.Next(3) + 4) };
Bitmap bmp = new Bitmap(codeW, codeH);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.Gray);
for (int i = 0; i < codes.Length; i++)
{
string code = codes[i];
Color c = colors[ChoiceColor()];
for (int j = 0; j < code.Length; j++)
{
int rotate = rnd.Next(60);
rotate = rotate < 30 ? 360 - rotate : rotate - 30;
int x = j * (codeW - 20) / code.Length + rnd.Next(20);
int y = rnd.Next(96) + 24;
DrawRotateTextAtPosition(g, code.Substring(j, 1), rotate, x, y, new Font(family[rnd.Next(family.Length)], rnd.Next(10) + 20, i == codes.Length - 1 ? FontStyle.Bold : FontStyle.Regular), new SolidBrush(c));
}
if (i == codes.Length - 1)
{
context.Session["dt_session_code"] = code;
CookieHelper.CommonSet("SessionID", CookieHelper.SubDomain, context.Session.SessionID);
g.FillRectangle(Brushes.SlateGray, new Rectangle(0, 0, 300, 24));
string txt = "请输入" + colorName.Substring(usedColor[usedColor.Count - 1], 1) + "色的字符";
for (int j = 0; j < txt.Length; j++)
{
g.DrawString(txt.Substring(j, 1), new Font("宋体", 16, j == 3 ? FontStyle.Bold : FontStyle.Regular), new SolidBrush(j == 3 ? c : colors[rnd.Next(colors.Length)]), new Point(36 * j, 2));
}
}
}
for (int i = 0; i < 500; i++)
{
int x3 = rnd.Next(bmp.Width - 1);
int y3 = rnd.Next(bmp.Height - 25) + 24;
Color clr = colors[rnd.Next(colors.Length)];
bmp.SetPixel(x3, y3, clr);
bmp.SetPixel(x3 + 1, y3, clr);
bmp.SetPixel(x3, y3 + 1, clr);
bmp.SetPixel(x3 + 1, y3 + 1, clr);
}
context.Response.Buffer = true;
context.Response.ExpiresAbsolute = DateTime.Now.AddMilliseconds(0.0);
context.Response.Expires = 0;
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Pragma", "No-Cache");
MemoryStream ms = new MemoryStream();
try
{
bmp.Save(ms, ImageFormat.Png);
context.Response.ClearContent();
context.Response.ContentType = "image/Png";
context.Response.BinaryWrite(ms.ToArray());
}
finally
{
bmp.Dispose();
g.Dispose();
}
}
private void DrawRotateTextAtPosition(Graphics g, string txt, float cornor, float x, float y, Font f, Brush b)
{
var fr = g.MeasureString(txt, f);
GraphicsState s = g.Save();
g.ResetTransform();
g.RotateTransform(cornor);
g.TranslateTransform(x + fr.Width / 2, y + fr.Height / 2, MatrixOrder.Append);
g.DrawString(txt, f, b, -fr.Width / 2, -fr.Height / 2);
g.Restore(s);
}
public string RandomString(string charset, int length)
{
if (string.IsNullOrEmpty(charset))
{
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
}
string result = string.Empty;
for (int i = 0; i < length; i++)
{
result += charset.Substring(rnd.Next(charset.Length), 1);
}
return result;
}
public bool IsReusable
{
get
{
return false;
}
}
}
这个代码需要将正确的验证码,保存到 session 里,所以,需要对IRequiresSessionState类进行继承。
对于字体来说,如果服务器不存在定义的字体,他会使用 Arial 字体来代替,所以,尽量使用服务器上存在的字体,或者在服务器上安装相应字体。
ChoiceColor 方法,是为了避免选中同样的颜色。
DrawRotateTextAtPosition 方法,则是为了在指定的位置显示旋转后的字符,如果直接使用 RotateTransform 然后直接 DrawString 的话,位置会乱掉,因为旋转的是当前画布。其中 x 轴的坐标,需要设定范围,避免字符前后顺序乱掉。还有就是,角度需要时正数,可以是浮点型。如果向回旋转,即角度小于0度,需要用 360 度加上负角度。
当然,老顾对 ai 也不了解,也不知道,ai 识别图片验证码,能有什么优势。。。
大概方式就是这样了,通过多种验证图形,放到一个验证码程序里,随机出一种效果,然后得到验证码即可。
还望各位读者给一些能简单实现的逻辑,用来生成验证码,嘿嘿。