ASP.NET Core 生成验证码
关注我
使用验证码保护网站免受垃圾信息的选择有很多,比如Google ReCaptcha和captcha.com。这两者都可以整合到ASP.NET Core应用中去。然而,如果你出于某些原因,仍然希望自己写
点击蓝字 关注我 使用验证码保护网站免受垃圾信息的选择有很多,比如Google ReCaptcha和captcha.com。这两者都可以整合到ASP.NET Core应用中去。然而,如果你出于某些原因,仍然希望自己写验证码,例如你下网站需要在中国大陆使用,那么本文会教你如何在最新版的ASP.NET Core中生成和使用验证码。 我所使用的方法是在微软样例代码库 的基础之上做了一些修改,以运行于.NET Core 2.x上,并也有一些改进。 验证码是如何工作的 一个简单的验证码原理是生成一串随机字符(数字或字母),将字符串保存到Session中,同时生成一张图片用来显示在网页上。当用户提交内容到服务器的时,服务器检查用户输入的验证码是否与Session中的一致,以此判断验证码是否正确。流程如下图: 这个样例是我下一版本博客中的验证码: 在 ASP.NET Core 2.1 中实现验证码 在了解验证码工作流程之后,我们来看看如何实现。 1 准备工作 首先,你需要在工程属性的Debug及Release模式里都勾选"Allow unsafe code"。 我们需要使用System.Drawing.Imaging 命名空间里的类型,所以我们也需要安装一个NuGet包: Install-Package System.Drawing.Common -Version 4.5.1 因为验证码依赖Session存储asp验证码,所以我们也需要在ASP.NET Core中启用Session支持。在Startup.cs里加入: public void ConfigureServices(IServiceCollection services) { // other code... // add session support services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(20); options.Cookie.HttpOnly = true; }); // other code... } 还有这里: public void Configure(IApplicationBuilder app, IHostingEnvironment env) { // other code... // add session support app.UseSession(); // other code... } 注意:Session依赖Cookie才能工作,所以请确保用户首先接受GDPR cookie策略,这是ASP.NET Core 2.1默认模板里添加的。 2 生成验证码 新建一个CaptchaResult类用来描述验证码信息: public class CaptchaResult { public string CaptchaCode { get; set; } public byte[] CaptchaByteData { get; set; } public string CaptchBase64Data => Convert.ToBase64String(CaptchaByteData); public DateTime Timestamp { get; set; } } 以及一个Captcha类来生成并验证验证码 public static class Captcha { const string Letters = "2346789ABCDEFGHJKLMNPRTUVWXYZ"; public static string GenerateCaptchaCode() { Random rand = new Random(); int maxRand = Letters.Length - 1; StringBuilder sb = new StringBuilder(); for (int i = 0; i < 4; i++) { int index = rand.Next(maxRand); sb.Append(Letters[index]); } return sb.ToString(); } public static bool ValidateCaptchaCode(string userInputCaptcha, HttpContext context) { var isValid = userInputCaptcha == context.Session.GetString("CaptchaCode"); context.Session.Remove("CaptchaCode"); return isValid; } public static CaptchaResult GenerateCaptchaImage(int width, int height, string captchaCode) { using (Bitmap baseMap = new Bitmap(width, height)) using (Graphics graph = Graphics.FromImage(baseMap)) { Random rand = new Random(); graph.Clear(GetRandomLightColor()); DrawCaptchaCode(); DrawDisorderLine(); AdjustRippleEffect(); MemoryStream ms = new MemoryStream(); baseMap.Save(ms, ImageFormat.Png); return new CaptchaResult { CaptchaCode = captchaCode, CaptchaByteData = ms.ToArray(), Timestamp = DateTime.Now }; int GetFontSize(int imageWidth, int captchCodeCount) { var averageSize = imageWidth / captchCodeCount; return Convert.ToInt32(averageSize); } Color GetRandomDeepColor() { int redlow = 160, greenLow = 100, blueLow = 160; return Color.FromArgb(rand.Next(redlow), rand.Next(greenLow), rand.Next(blueLow)); } Color GetRandomLightColor() { int low = 180, high = 255; int nRend = rand.Next(high) % (high - low) + low; int nGreen = rand.Next(high) % (high - low) + low; int nBlue = rand.Next(high) % (high - low) + low; return Color.FromArgb(nRend, nGreen, nBlue); } void DrawCaptchaCode() { SolidBrush fontBrush = new SolidBrush(Color.Black); int fontSize = GetFontSize(width, captchaCode.Length); Font font = new Font(FontFamily.GenericSerif, fontSize, FontStyle.Bold, GraphicsUnit.Pixel); for (int i = 0; i < captchaCode.Length; i++) { fontBrush.Color = GetRandomDeepColor(); int shiftPx = fontSize / 6; float x = i * fontSize + rand.Next(-shiftPx, shiftPx) + rand.Next(-shiftPx, shiftPx); int maxY = height - fontSize; if (maxY < 0) maxY = 0; float y = rand.Next(0, maxY); graph.DrawString(captchaCode[i].ToString(), font, fontBrush, x, y); } } void DrawDisorderLine() { Pen linePen = new Pen(new SolidBrush(Color.Black), 3); for (int i = 0; i < rand.Next(3, 5); i++) { linePen.Color = GetRandomDeepColor(); Point startPoint = new Point(rand.Next(0, width), rand.Next(0, height)); Point endPoint = new Point(rand.Next(0, width), rand.Next(0, height)); graph.DrawLine(linePen, startPoint, endPoint); //Point bezierPoint1 = new Point(rand.Next(0, width), rand.Next(0, height)); //Point bezierPoint2 = new Point(rand.Next(0, width), rand.Next(0, height)); //graph.DrawBezier(linePen, startPoint, bezierPoint1, bezierPoint2, endPoint); } } void AdjustRippleEffect() { short nWave = 6; int nWidth = baseMap.Width; int nHeight = baseMap.Height; Point[,] pt = new Point[nWidth, nHeight]; for (int x = 0; x < nWidth; ++x) { for (int y = 0; y < nHeight; ++y) { var xo = nWave * Math.Sin(2.0 * 3.1415 * y / 128.0); var yo = nWave * Math.Cos(2.0 * 3.1415 * x / 128.0); var newX = x + xo; var newY = y + yo; if (newX > 0 && newX < nWidth) { pt[x, y].X = (int)newX; } else { pt[x, y].X = 0; } if (newY > 0 && newY < nHeight) { pt[x, y].Y = (int)newY; } else { pt[x, y].Y = 0; } } } Bitmap bSrc = (Bitmap)baseMap.Clone(); BitmapData bitmapData = baseMap.LockBits(new Rectangle(0, 0, baseMap.Width, baseMap.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); BitmapData bmSrc = bSrc.LockBits(new Rectangle(0, 0, bSrc.Width, bSrc.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); int scanline = bitmapData.Stride; IntPtr scan0 = bitmapData.Scan0; IntPtr srcScan0 = bmSrc.Scan0; unsafe { byte* p = (byte*)(void*)scan0; byte* pSrc = (byte*)(void*)srcScan0; int nOffset = bitmapData.Stride - baseMap.Width * 3; for (int y = 0; y < nHeight; ++y) { for (int x = 0; x < nWidth; ++x) { var xOffset = pt[x, y].X; var yOffset = pt[x, y].Y; if (yOffset >= 0 && yOffset < nHeight && xOffset >= 0 && xOffset < nWidth) { if (pSrc != null) { p[0] = pSrc[yOffset * scanline + xOffset * 3]; p[1] = pSrc[yOffset * scanline + xOffset * 3 + 1]; p[2] = pSrc[yOffset * scanline + xOffset * 3 + 2]; } } p += 3; } p += nOffset; } } baseMap.UnlockBits(bitmapData); bSrc.UnlockBits(bmSrc); bSrc.Dispose(); } } } } 这么长的代码你竟然看完了! 有一些要指出的地方: 1 字符集并不包含全部的字母和数字,这是因为有些数字和英文字母难以区分,比如: 2 我注释掉了DrawDisorderLine()方法中的贝塞尔曲线,这是因为当验证码图片非常小的时候,贝塞尔曲线会干扰字符显示,看不清验证码。 现在,在你的MVC控制器中,创建一个Action用于返回验证码图片: [Route("get-captcha-image")] public IActionResult GetCaptchaImage() { int width = 100; int height = 36; var captchaCode = Captcha.GenerateCaptchaCode(); var result = Captcha.GenerateCaptchaImage(width, height, captchaCode); HttpContext.Session.SetString("CaptchaCode", result.CaptchaCode); Stream s = new MemoryStream(result.CaptchaByteData); return new FileStreamResult(s, "image/png"); } 现在,尝试访问这个Action,你应该能看到像这样的验证码图片: 3 使用验证码 在你需要提交内容到服务器端的model里加入一个新的属性,叫做CaptchaCode [Required] [StringLength(4)] public string CaptchaCode { get; set; } 在View中加入一个对应CaptchaCode的输入框,以及一个调用GetCaptchaImage的图片 在处理用户提交数据的Action中加入检查验证码的逻辑 if (ModelState.IsValid) { // Validate Captcha Code if (!Captcha.ValidateCaptchaCode(model.CaptchaCode, HttpContext)) { // return error } // continue business logic } 4 再完善一点 你可以用jQuery实现用户点击图片刷新验证码 $("#img-captcha").click(function () { resetCaptchaImage(); }); function resetCaptchaImage() { d = new Date(); $("#img-captcha").attr("src", "/get-captcha-image?" + d.getTime()); } (编辑:好传媒网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |