驗證碼是防止暴力破解、機器人惡意註冊與自動化腳本攻擊的第一道防線。本篇分享一個基於 ASHX 一般處理常式實作的專業級驗證碼生成器,支援干擾線、干擾點、隨機字型與色彩,並採用 Session 儲存驗證碼值供後續驗證。
using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using System.Web.SessionState;
namespace App.Admin.Core {
public class VerifyCode : IHttpHandler, IRequiresSessionState {
public void ProcessRequest(HttpContext context) {
// 圖片尺寸與字型設定
int codeW = 80, codeH = 22, fontSize = 16;
string chkCode = string.Empty;
// 色彩池:驗證碼字元、干擾線、干擾點共用
Color[] colors = { Color.Black, Color.Red, Color.Blue, Color.Green,
Color.Orange, Color.Brown, Color.DarkBlue };
// 字型池:增加識別難度
string[] fonts = { "Times New Roman", "Verdana", "Arial", "Gungsuh", "Impact" };
// 字元池:排除易混淆字元 (0/O, 1/I/l, 8/B 等)
char[] chars = { '2','3','4','5','6','8','9',
'a','b','d','e','f','h','k','m','n','r','x','y',
'A','B','C','D','E','F','G','H','J','K','L','M','N','P','R','S','T','W','X','Y' };
Random rnd = new Random();
// 生成 4 位驗證碼
for (int i = 0; i < 4; i++) {
chkCode += chars[rnd.Next(chars.Length)];
}
// 存入 Session 供驗證使用
context.Session["VerifyCode"] = chkCode;
// 建立畫布
using (Bitmap bmp = new Bitmap(codeW, codeH))
using (Graphics g = Graphics.FromImage(bmp)) {
g.Clear(Color.White);
// 繪製 1 條干擾線
for (int i = 0; i < 1; i++) {
int x1 = rnd.Next(codeW), y1 = rnd.Next(codeH);
int x2 = rnd.Next(codeW), y2 = rnd.Next(codeH);
g.DrawLine(new Pen(colors[rnd.Next(colors.Length)]), x1, y1, x2, y2);
}
// 繪製驗證碼字元(每字元隨機字型、顏色、位置微調)
for (int i = 0; i < chkCode.Length; i++) {
Font ft = new Font(fonts[rnd.Next(fonts.Length)], fontSize);
Color clr = colors[rnd.Next(colors.Length)];
g.DrawString(chkCode[i].ToString(), ft, new SolidBrush(clr), (float)i * 18 + 2, 0);
}
// 繪製 100 個干擾點
for (int i = 0; i < 100; i++) {
bmp.SetPixel(rnd.Next(bmp.Width), rnd.Next(bmp.Height), colors[rnd.Next(colors.Length)]);
}
// 關鍵:禁用快取,確保每次請求都是新驗證碼
context.Response.Buffer = true;
context.Response.ExpiresAbsolute = DateTime.Now.AddMilliseconds(0);
context.Response.Expires = 0;
context.Response.CacheControl = "no-cache";
context.Response.AppendHeader("Pragma", "No-Cache");
// 輸出 PNG 圖片串流
context.Response.ContentType = "image/png";
using (MemoryStream ms = new MemoryStream()) {
bmp.Save(ms, ImageFormat.Png);
context.Response.BinaryWrite(ms.ToArray());
}
}
}
public bool IsReusable { get { return false; } }
}
}
| 技術點 | 實作細節 | 安全效果 |
|---|---|---|
| 字元池過濾 | 移除 0/O, 1/I/l, 8/B, 5/S, 2/Z 等易混淆字元 | 降低使用者辨識錯誤率 |
| 隨機字型與色彩 | 每字元獨立隨機選擇字型、大小、顏色 | 破壞 OCR 字元分割與識別 |
| 干擾線與干擾點 | 幾何干擾線 + 高密度雜訊點 | 干擾影像二值化與骨架提取 |
| Session 儲存 | 伺服端 Session 儲存,非 Cookie 或隱藏欄位 | 防止客戶端竄改或繞過驗證 |
| 完整快取控制 | Expires=0, Cache-Control=no-cache, Pragma=No-Cache | 防止瀏覽器快取導致驗證碼不更新 |
| 資源釋放 | using 區塊確保 Bitmap、Graphics、MemoryStream 正確 Dispose | 防止非受控資源洩漏導致 GDI+ 錯誤 |
<img id="imgVerifyCode" src="/Core/VerifyCode.ashx" alt="驗證碼" style="cursor:pointer; vertical-align:middle;" />
<a href="javascript:void(0);" onclick="$('#imgVerifyCode').attr('src', '/Core/VerifyCode.ashx?' + Math.random());">看不清?換一張</a>
<input type="text" id="txtVerifyCode" name="VerifyCode" placeholder="請輸入驗證碼" />
[HttpPost]
public JsonResult Login(LoginViewModel model) {
string sessionCode = Session["VerifyCode"]?.ToString();
if (string.IsNullOrEmpty(sessionCode) ||
!sessionCode.Equals(model.VerifyCode, StringComparison.OrdinalIgnoreCase)) {
return Json(new { success = false, message = "驗證碼錯誤" });
}
// 後續登入驗證邏輯...
}
Graphics.DrawBezier 繪製貝茲曲線替代直線System.Drawing,IIS 應用程式集區身分識別是否具備 GDI+ 權限IRequiresSessionState 介面已實作,web.config 未停用 Session此驗證碼機制已在生產環境穩定運行,兼顧安全性與使用者體驗,可直接整合至任何 ASP.NET MVC/Web Forms 專案中。