发布于2026-07-17 阅读(0)
扫一扫,手机访问
先说结论:用 System.Drawing 在 .NET 6+ 的跨平台项目里生成验证码图片,这条路基本走不通。要么开启兼容模式,要么直接换成 SkiaSharp。如果是传统的 .NET Framework 4.x 项目,用 Graphics + Bitmap 没什么问题,但要注意字体嵌入和抗锯齿带来的干扰。
这篇文章会把报错原因、跨平台替换方案、以及 ASP.NET Core 中的安全绑定逻辑一次讲清楚。开始之前,先交代一个关键判断:真正麻烦的从来不是画图本身,而是如何在“防机器识别”和“不折磨真实用户”之间找到平衡。
最常见的翻车姿势是 System.PlatformNotSupportedException: System.Drawing.Common is not supported on this platform。从 .NET Core 3.0 开始,微软明确将 System.Drawing 标记为仅 Windows 平台兼容的库,Linux 和 macOS 上的容器或跨平台部署场景默认禁用 GDI+。这个坑几乎每个从 .NET Framework 迁移过来的开发者都踩过。
解决办法不是加个包就完事了——必须显式声明兼容性。在 .csproj 中添加以下配置:
true
但要注意,这个方案只对 Windows 部署场景有效。如果用 Docker 跑应用,哪怕基础镜像选的是 mcr.microsoft.com/dotnet/aspnet:6.0-windowsservercore,还得确认 GDI+ 组件是否安装到位。相比之下,更稳妥的做法是直接切换到 SkiaSharp——它不依赖系统图形栈,跨平台兼容性好,而且能轻松实现中文、旋转、噪点这些进阶需求。
核心流程不复杂:创建 SKSurface -> 获取 SKCanvas -> 逐字符绘制(带随机角度) -> 加噪点 -> 输出 PNG 流。但在细节处有几个容易翻车的点,值得单独拎出来说:
RotateDegrees 对整个画布做全局旋转,否则干扰线和背景元素都会跟着歪。正确做法是每个字符单独设置 SKPaint.TextScaleX/Y 和 TextAlign,然后通过平移定位。SKPath 绘制贝塞尔曲线,比直线更难被 OCR 识别。颜色上别用纯黑(SKColors.Black),改用 new SKColor(80, 80, 80) 降低对比度,既不影响人眼识别,又能增加机器识别难度。CopyToOutputDirectory,加载时用 SKTypeface.FromFile("simhei.ttf"),千万不要依赖系统字体名称(比如 "SimHei" 在 Linux 上不存在)。下面是一个生成4字符验证码的关键代码片段:
using (var surface = SKSurface.Create(new SKImageInfo(120, 40)))
{
var canvas = surface.Canvas;
canvas.Clear(SKColors.White);
var typeface = SKTypeface.FromFile("simhei.ttf");
var paint = new SKPaint { Typeface = typeface, TextSize = 24 };
string code = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
var chars = Enumerable.Range(0, 4).Select(_ => code[new Random().Next(code.Length)]).ToArray();
for (int i = 0; i < chars.Length; i++)
{
paint.Color = new SKColor((byte)new Random().Next(50, 150),
(byte)new Random().Next(50, 150),
(byte)new Random().Next(50, 150));
canvas.DrawText(chars[i].ToString(), 20 + i * 25, 30 + (float)(Math.Sin(i * 0.5) * 5), paint);
}
var img = surface.Snapshot();
var data = img.Encode(SKEncodedImageFormat.Png, 100);
return File(data.ToArray(), "image/png");
}
验证码值绝对不能放在客户端(比如 hidden field),必须在服务端存储并与当前请求上下文强绑定。不少开发者写进 HttpContext.Session.SetString 后就忽略了过期和并发问题,这是典型的安全漏洞。
以下几点是必须做到的:
HttpContext.Session.SetString("CaptchaCode", code) 的同时,用 HttpContext.Session.SetInt32("CaptchaExpire", DateTime.Now.AddMinutes(5).ToBinary()) 记录时间戳。if (DateTime.FromBinary(HttpContext.Session.GetInt32("CaptchaExpire") ?? 0) < DateTime.Now) 时直接拒绝。Guid.NewGuid().ToString() 跟用户 Session 无关,攻击者可以复用同一个验证码图片反复提交。正确的做法是用 HttpContext.Session.Id 进行关联。IDistributedCache 存储,key 拼接成 "captcha_" + HttpContext.Session.Id。坦率说,真正考验产品能力的地方不在于画图,而在于平衡。噪点撒过头,移动端截图识别率直接暴跌;字间距窄得亲爹都看不清,视力不佳的用户直接放弃。这些都不是靠几行模板代码能解决的,必须根据具体业务场景的用户反馈反复调优。没有银弹,只有迭代。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8