早期驗證碼多採用預製數字/字母圖片拼接,靈活性差、素材維護成本高。現代方案採用 GDI+ 即時繪製單張圖片,可任意控制字元集、干擾線、噪點、扭曲變形,安全性與靈活性雙贏。
Bitmap 畫布,創建 Graphics 繪圖面;Graphics.DrawString 繪製字元;Response.OutputStream;Session,後續表單提交時比對驗證。
public enum RandomGeneratorStyle {
Number, // 純數字
NumberAndChar, // 數字 + 大小寫字母
NumberAndCharIgnoreCase // 數字 + 大寫字母
}
public static class RandomGenerator {
private static readonly Random _rnd = new Random();
public static string Generate(RandomGeneratorStyle style, int length) {
string source = style switch {
RandomGeneratorStyle.Number => "0123456789",
RandomGeneratorStyle.NumberAndChar => "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ",
RandomGeneratorStyle.NumberAndCharIgnoreCase => "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
_ => "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
};
char[] chars = new char[length];
for (int i = 0; i < length; i++) {
chars[i] = source[_rnd.Next(source.Length)];
}
return new string(chars);
}
}
public class CaptchaHandler : IHttpHandler {
public bool IsReusable => false;
public void ProcessRequest(HttpContext context) {
// 參數:長度、樣式、寬高可從 QueryString 讀取,這裡用預設值
int length = 4;
RandomGeneratorStyle style = RandomGeneratorStyle.NumberAndCharIgnoreCase;
int width = (int)Math.Ceiling(length * 18.5); // 字體 14px 寬度約 18.5
int height = 30;
string code = RandomGenerator.Generate(style, length);
context.Session["CaptchaCode"] = code; // 存 Session 驗證
using (Bitmap bmp = new Bitmap(width, height))
using (Graphics g = Graphics.FromImage(bmp)) {
// 背景
g.Clear(Color.White);
// 干擾線
DrawNoiseLines(g, width, height, 3);
// 干擾點
DrawNoisePoints(g, width, height, 50);
// 字體
using (Font font = new Font("Consolas", 16, FontStyle.Bold | FontStyle.Italic))
using (SolidBrush brush = new SolidBrush(Color.FromArgb(_rnd.Next(0, 100), _rnd.Next(0, 100), _rnd.Next(0, 100)))) {
// 逐字元繪製,隨機偏移、旋轉增加識別難度
for (int i = 0; i < code.Length; i++) {
float x = i * 18 + _rnd.Next(2);
float y = _rnd.Next(-2, 4);
g.TranslateTransform(x, y);
g.RotateTransform(_rnd.Next(-15, 15));
g.DrawString(code[i].ToString(), font, brush, 0, 0);
g.ResetTransform();
}
}
// 輸出
context.Response.ContentType = "image/jpeg";
bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
}
private void DrawNoiseLines(Graphics g, int w, int h, int count) {
using (Pen pen = new Pen(Color.LightGray, 1)) {
for (int i = 0; i < count; i++) {
g.DrawLine(pen, _rnd.Next(w), _rnd.Next(h), _rnd.Next(w), _rnd.Next(h));
}
}
}
private void DrawNoisePoints(Graphics g, int w, int h, int count) {
using (SolidBrush brush = new SolidBrush(Color.Gray)) {
for (int i = 0; i < count; i++) {
g.FillRectangle(brush, _rnd.Next(w), _rnd.Next(h), 1, 1);
}
}
}
}
<img src="CaptchaHandler.ashx" id="captchaImg" alt="驗證碼" style="cursor:pointer;vertical-align:middle;" />
<a href="javascript:void(0);" onclick="document.getElementById('captchaImg').src='CaptchaHandler.ashx?' + Math.random();">看不清?換一張</a>
<br />
<input type="text" id="txtCaptcha" name="captcha" maxlength="4" />
public static bool Validate(string userInput, HttpContext context) {
if (context.Session["CaptchaCode"] is string serverCode) {
// 不區分大小寫比對
return string.Equals(userInput, serverCode, StringComparison.OrdinalIgnoreCase);
}
return false;
}
// 在登入/提交 Action 中調用
if (!CaptchaHelper.Validate(Request.Form["captcha"], HttpContext.Current)) {
ModelState.AddModelError("captcha", "驗證碼錯誤");
return View();
}
| 增強項 | 實現思路 |
|---|---|
| 字元扭曲變形 | 使用 GraphicsPath.AddString + GraphicsPath.Warp 對字元路徑做隨機透視變形 |
| 背景漸變/圖片 | LinearGradientBrush 或 TextureBrush 填充背景 |
| 中文驗證碼 | 字元源改為常用漢字庫,字體選「微軟雅黑」,寬度適當加大 |
| 點擊/滑塊/行為驗證 | 前端集成 GeeTest、滑動拼圖、點選文字等無感驗證 SDK,後端二次校驗 |
| 分散式 Session | Session 改用 Redis、資料庫,支援 Web Farm 負載均衡 |
SessionState 模式正確(InProc/StateServer/SQLServer/Redis),驗證碼頁面與表單提交在同一應用域。Response.ContentType、無多餘輸出、HttpHandler 註冊正確(web.config <handlers> 或 .ashx 自動映射)。Random 實例化頻率過高會產生相同種子,改用 static readonly Random 或 RandomNumberGenerator (CSP)。alt 屬性提示「驗證碼圖片,點擊更換」。GDI+ 繪製驗證碼是 ASP.NET 經典技能,核心在於 Bitmap + Graphics + DrawString 三板斧。本文提供從隨機生成、干擾繪製、Session 存儲到驗證比對的完整閉環代碼,並給出進階強化方向。實際專案中可根據安全等級選擇「簡單圖形驗證碼」或「第三方行為驗證」,兼顧用戶體驗與防刷效果。