专栏首页汪宇杰博客ASP.NET Core 生成验证码

ASP.NET Core 生成验证码

使用验证码保护网站免受垃圾信息的选择有很多,比如Google ReCaptchacaptcha.com。这两者都可以整合到ASP.NET Core应用中去。然而,如果你出于某些原因,仍然希望自己写验证码,例如你下网站需要在中国大陆使用,那么本文会教你如何在最新版的ASP.NET Core中生成和使用验证码。

我所使用的方法是在微软样例代码库 https://code.msdn.microsoft.com/How-to-make-and-use-d0d1752a 的基础之上做了一些修改,以运行于.NET Core 2.x上,并也有一些改进。

验证码是如何工作的

一个简单的验证码原理是生成一串随机字符(数字或字母),将字符串保存到Session中,同时生成一张图片用来显示在网页上。当用户提交内容到服务器的时,服务器检查用户输入的验证码是否与Session中的一致,以此判断验证码是否正确。流程如下图:

这个样例是我下一版本博客中的验证码:

在 ASP.NET Core 2.1 中实现验证码

在了解验证码工作流程之后,我们来看看如何实现。

1

准备工作

首先,你需要在工程属性的DebugRelease模式里都勾选"Allow unsafe code"。

我们需要使用 System.Drawing.Imaging 命名空间里的类型,所以我们也需要安装一个NuGet包:

Install-Package System.Drawing.Common -Version 4.5.1

因为验证码依赖Session存储,所以我们也需要在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

字符集并不包含全部的字母和数字,这是因为有些数字和英文字母难以区分,比如:

  • 数字0和字母O
  • 数字5和字母S
  • 数字1和字母I

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的图片
<div class="input-group">
    <div class="input-group-prepend">
        <img id="img-captcha" data-src="~/get-captcha-image" />
    </div>
    <input type="text" class="form-control" placeholder="Captcha Code" asp-for="CaptchaCode" maxlength="4" />
    <span asp-validation-for="CaptchaCode" class="text-danger"></span>
</div>
在处理用户提交数据的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());
}

本文分享自微信公众号 - 汪宇杰博客(ediwangblog),作者:汪宇杰

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-10-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • ASP.NET Core 给上传的图片加水印

    图片加水印是网站中使用非常广泛的技术,可以保护网站内容的版权,例如我博客这样的网站。在传统ASP.NET(.NET Framework)中,我们可以使用Syst...

    Edi Wang
  • Azure 上使用 Windows Server Core 运行 ASP.NET Core 网站

    微软智慧云 Azure 上虽然早就有 App Service 这种完全托管的 PaaS 服务可以让我们分分钟建网站。但是不自己配一下环境,就不能体现技术含量,容...

    Edi Wang
  • ASP.NET Core 2.2: SignalR的Java客户端

    在 ASP.NET Core 2.2中,我们将为 SignalR 新增一个 Java 客户端。此新客户端现在可用。此客户端支持从 Java 代码 (包括 And...

    Edi Wang
  • asp.net 加入验证码

    冰封一夏
  • 明了 | MongoDB 外键的基本使用

    而在MongoDB中,表示表关系,使用的是嵌套,即,一个文档嵌套一个文档的方法,作为MongoDB的两个文档的关联,以及使用,reference link作为文...

    mySoul
  • 静态化技术Freemarker

    FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出。FreeMarker与Web容器无关,即在Web运行时,它并不知道Servle...

    HUC思梦
  • Tensorflow-gpu 运行在 cpu 母机的问题

    tensorflow-gpu 的镜像当然运行在 GPU 的母机上了,但是如果容器被调度到没有 GPU 的母机上呢?

    runzhliu
  • 《外卖骑手,困在系统里》:一场事先张扬的口诛笔伐

    早在那篇万字文章发布之前,其实有关外卖骑手生存状态的话题早就已经开始酝酿,只不过那些酝酿仅仅只是星星之火,并未形成燎原之势而已。

    孟永辉
  • Java核心编程快速入门

    Java核心编程部分的基础学习内容就不一一介绍了,本文的重点是JAVA中相对复杂的一些概念,主体内容如下图所示。 ? ? 反射reflect是理解Java语言...

    用户1216676
  • 用scikit-learn学习DBSCAN聚类

        在DBSCAN密度聚类算法中,我们对DBSCAN聚类算法的原理做了总结,本文就对如何用scikit-learn来学习DBSCAN聚类做一个总结,重点讲述...

    刘建平Pinard

扫码关注云+社区

领取腾讯云代金券