system.drawing 可在 windows 环境下生成带干扰线和轻微旋转扭曲的验证码图,需选用兼容字体、控制干扰线数量(3–5 条)、设置透明度及±8°内随机旋转字符。

怎么用 System.Drawing 画出带干扰线和扭曲字符的验证码图
直接用 System.Drawing 是最轻量、不依赖第三方库的做法,但要注意 .NET Core 3.0+ 默认不支持它——Windows 上还能跑,Linux/macOS 会直接报 TypeInitializationException 或提示 GDI+ 不可用。所以第一步得确认运行环境。
实操建议:
- 若项目跑在 Windows Server 或 Win10+,且用的是 .NET Framework 4.7.2 或 .NET 5/6/7 的 Windows-only 部署,
System.Drawing可用;否则优先换SkiaSharp - 字符绘制别用默认字体:用
"Arial"或"Microsoft Sans Serif",避免某些系统缺字导致乱码或空格 - 干扰线数量控制在 3–5 条,太多影响识别;用
Pen设置透明度(Color.FromArgb(80, r, g, b))比纯黑更自然 - 字符扭曲靠
Graphics.Transform+ 小角度随机旋转(±8°以内),别用大变形,OCR 工具一扫就崩
示例关键片段:
var bitmap = new Bitmap(120, 40); using var g = Graphics.FromImage(bitmap); g.Clear(Color.White); // 绘制干扰线... for (int i = 0; i <h3>生成随机字符串时为什么不能只用 <code>Random</code> 类</h3> <p><code>Random</code> 在高并发请求下会重复——比如 Web API 被连续调用,多个线程几乎同时 new <code>Random()</code>,它们共享同一个时间种子,输出序列大概率一致。验证码撞车就等于形同虚设。</p> <p>实操建议:</p>
- 全局复用一个
Random实例(static readonly),别每次 new - 更稳妥用
RNGCryptoServiceProvider(.NET Framework)或RandomNumberGenerator(.NET Core+),它基于系统熵源,不可预测 - 字符集别硬写
"0123456789abcdefghijklmnopqrstuvwxyz":剔除易混淆字符如'0'(零)、'O'(大写字母 O)、'l'(小写 L)、'I'(大写 i),推荐用"23456789ABCDEFGHJKMNPQRSTUVWXYZ" - 长度固定为 4 或 5 位,太短易暴力,太长用户输错率陡增
验证码图片怎么传给前端又不被缓存或盗刷
浏览器缓存、代理缓存、CDN 缓存都会让同一张图反复返回,后端生成了新验证码,前端却还拿着旧图提交,必然失败。更麻烦的是,攻击者能直接抓包反复请求同一 URL 拿到大量有效图。
实操建议:
- 接口路径必须带唯一参数,例如
/captcha?r=123e4567-e89b-12d3-a456-426614174000,每次前端 JS 调用前生成新 UUID - 响应头强制禁用缓存:
Response.Headers.Add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")和Expires: -1 - 服务端生成验证码后,立即存入内存缓存(如
MemoryCache)或 Redis,设置 3 分钟过期;键名用该次请求的 token(比如 UUID),值是明文字符串 - 别把验证码塞进 Session——分布式部署时 Session 不共享,校验必失败
校验时为什么 CompareOrdinal 比 Equals 更安全
用户输入可能带全角空格、不可见 Unicode 字符(比如零宽空格 \u200B),用默认字符串比较会误判相等。更隐蔽的是大小写处理:有些前端自动转成大写再提交,后端如果用 StringComparison.OrdinalIgnoreCase,攻击者可构造 "aaaa" 和 "AAAA" 碰撞哈希(虽然概率低,但没必要留口子)。
实操建议:
- 接收参数后先做标准化清洗:
input.Trim().Replace("\u200B", "").Replace("\uFEFF", "") - 校验用
string.Equals(storedCode, userInput, StringComparison.Ordinal),严格字节一致 - 校验通过或失败后,立刻从缓存删掉该验证码——哪怕用户刷新页面重试,旧图也作废
- 单 IP 5 分钟内连续输错 5 次,临时封禁该 IP 的验证码发放接口(简单计数器即可,不用上 Redis)
真正难的不是画图,是让每一张图从生成、传输、显示到校验,全程不被缓存污染、不被并发打穿、不被 OCR 批量识别。这些环节里,任意一个松动,验证码就只是心理安慰。











