最轻量安全的验证码实现是直接用Masuit.Tools的ValidateCode.CreateValidateCode(6)生成6位字符串,再调用CreateValidateGraphic(28)转为PNG图片流,自动启用干扰线、干扰点、渐变文字及字体探测,避免System.Drawing跨平台问题与内存泄漏。

直接用 ValidateCode.CreateValidateCode() 生成字符串 + CreateValidateGraphic() 转图片,是最轻量、最安全的起点。它默认启用干扰线、干扰点和渐变文字,不依赖 GDI+(避免 Windows Server 容器中崩溃),也不强求 SkiaSharp(省去跨平台编译麻烦)。
为什么别手写 Random + Graphics 绘图
自己用 System.Drawing.Graphics 手绘验证码,看似可控,实则埋雷:
-
System.Drawing在 .NET 6+ 非 Windows 环境(如 Linux Docker)下默认不可用,会抛出System.TypeInitializationException: The type initializer for 'Gdip' threw an exception. - 手动加噪点/扭曲时,容易漏掉关键防护:比如没做字符间距随机化,OCR 工具用模板匹配就能绕过
- 字体硬编码(如
"Arial")在 Alpine Linux 容器里根本不存在,图片生成直接空白或报错Font 'Arial' not found - 每次 new Bitmap → Graphics → Save → Dispose 流程若没用
using包裹,内存泄漏极快,高并发下 IIS 或 Kestrel 进程可能被 OOM 杀掉
Masuit.Tools 的 ValidateCode 怎么用才不踩坑
它封装了字体自动探测、内存池复用和抗 OCR 干扰逻辑,但几个参数必须显式设对:
- 生成字符串时,别用默认长度:
ValidateCode.CreateValidateCode(4)安全性不足,建议固定为6——ValidateCode.CreateValidateCode(6) - 调用
CreateValidateGraphic()时,字号传28是经验值:太小(如16)干扰点易覆盖文字;太大(如40)留白少,干扰线密度下降 - 务必用
PooledMemoryStream接收输出流,而不是new MemoryStream():using var imageStream = code.CreateValidateGraphic(28); - ASP.NET Core 中返回图片时,Content-Type 必须是
image/png,不是image/jpeg—— PNG 支持透明通道,干扰点边缘更自然,JPEG 压缩反而强化字符轮廓,利于 OCR
SkiaSharp 方案只在需要深度定制时考虑
如果你必须控制每个像素(比如画鸭子图形验证码、滑块缺口、点击区域热区),SkiaSharp 是唯一靠谱选择,但代价明确:
- 要额外引用
SkiaSharp和SkiaSharp.NativeAssets.Linux(Linux 容器)或SkiaSharp.NativeAssets.Windows(Windows Server) - 初始化
SKBitmap时宽高必须是整数,且建议避开奇数值(如121x41),某些 GPU 驱动对非 2^n 尺寸渲染有偏移 bug -
SKPaint的IsAntialias = true必须开启,否则文字锯齿严重,真人识别困难,但机器反而更容易二值化提取 - 干扰线不能只画直线:至少混入 20% 的贝塞尔曲线(
canvas.DrawPath()),纯直线干扰在现代 OCR 模型里基本无效
真正容易被忽略的是服务端存储与校验环节:生成的验证码字符串必须存进 Redis(带 5 分钟 TTL),且 key 要绑定客户端 IP + User-Agent 指纹,否则攻击者用同一张图反复提交就能绕过。图片本身不存,只存文本;流式输出完立刻丢弃内存流,别缓存字节数组。


















