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中的一致,以此判断验证码是否正确。流程如下图:

640?wx_fmt=png

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

640?wx_fmt=png

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

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

640?wx_fmt=gif1

准备工作

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

640?wx_fmt=png

我们需要使用 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默认模板里添加的。smiley_11.png

640?wx_fmt=gif

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();

            }

        }

    }

}

这么长的代码你竟然看完了!smiley_26.png

640?wx_fmt=jpeg

有一些要指出的地方:

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,你应该能看到像这样的验证码图片:

640?wx_fmt=png640?wx_fmt=gif

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

}

640?wx_fmt=jpeg

4

再完善一点

你可以用jQuery实现用户点击图片刷新验证码smiley_13.png

$("#img-captcha").click(function () {

    resetCaptchaImage();

});


function resetCaptchaImage() {

    d = new Date();

    $("#img-captcha").attr("src", "/get-captcha-image?" + d.getTime());

}

640?wx_fmt=gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/318960.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P4756-Added Sequence【斜率优化】

正题 题目链接:https://www.luogu.com.cn/problem/P4756 题目大意 给出序列aaa&#xff0c;设f(l,r)∣∑ilrai∣f(l,r)|\sum_{il}^ra_i|f(l,r)∣∑ilr​ai​∣。 mmm次询问若序列aaa全部加上某个数xxx&#xff0c;求最大的f(l,r)f(l,r)f(l,r)。 1≤n,m≤21051\leq n,m\leq 2\…

P4564 [CTSC2018]假面(期望)

P4564 [CTSC2018]假面 首先容易看出结界技能对第二问敌方剩余生命值期望没有影响。 如何求出第iii个人的剩余生命值期望&#xff1f; 只需要根据Ei∑j0aijfi,jE_i\sum_{j0}^{a_i}jf_{i,j}Ei​∑j0ai​​jfi,j​ 预处理fi,jf_{i,j}fi,j​&#xff1a;第iii个人的剩余生命值为j…

牛客题霸 [连续子数组的最大和] C++题解/答案

牛客题霸 [连续子数组的最大和] C题解/答案 题目描述 HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含…

P4234-最小差值生成树【LCT】

正题 题目链接:https://www.luogu.com.cn/problem/P4234 题目大意 给出nnn个点mmm条边的一张图。求一棵生成树使得最大边权减去最小边权最小。 1≤n≤5104,1≤m≤21051\leq n\leq 5\times 10^4,1\leq m\leq 2\times 10^51≤n≤5104,1≤m≤2105 解题思路 按照边权排序&#x…

【.NET Core项目实战-统一认证平台】第六章 网关篇-自定义客户端授权

上篇文章【.NET Core项目实战-统一认证平台】第五章 网关篇-自定义缓存Redis 我们介绍了网关使用Redis进行缓存&#xff0c;并介绍了如何进行缓存实现&#xff0c;缓存信息清理接口的使用。本篇我们将介绍如何实现网关自定义客户端授权&#xff0c;实现可以为不同的接入客户端设…

2287. 【POJ Challenge】消失之物(数组递推\分治优化背包)

2287. 【POJ Challenge】消失之物 这题的思想和P4564 [CTSC2018]假面优化的思想一样&#xff0c;应该反过来说&#xff0c;假面那个题应该是借鉴这题的思路。 显然不能枚举每个物品消失O(n)O(n)O(n)&#xff0c;然后跑背包O(nm)O(nm)O(nm) 预处理dp fjf_jfj​表示nnn个物品装…

如何用ABP框架快速完成项目(面向项目交付编程面向客户编程篇) - 广州.net微软技术俱乐部12月份活动报名帖...

这是广州.net微软技术俱乐部12月份活动报名帖。此帖会持续更新。活动课程标题是&#xff1a;如何用ABP框架快速完成项目(面向项目交付编程面向客户编程篇)这是内容大纲&#xff1a;ABP框架简介&#xff08;这里会聊聊.net真的不如JAVA吗&#xff1f;&#xff09;快的定义!用ABP…

P5012-水の数列【并查集,RMQ】

正题 题目链接:https://www.luogu.com.cn/problem/P5012 题目大意 nnn个数字的一个序列&#xff0c;TTT次询问给出[l,r][l,r][l,r]要求 找出一个最大的xxx满足。提出所有的小于xxx的数&#xff0c;然后被提出的数的连续区间长度平方和除以xxx的值最大要求分出来的区间个数在[…

牛客题霸 [ 换钱的最少货币数] C++题解/答案

牛客题霸 [ 换钱的最少货币数] C题解/答案 题目描述 给定数组arr&#xff0c;arr中所有的值都为正整数且不重复。每个值代表一种面值的货币&#xff0c;每种面值的货币可以使用任意张&#xff0c;再给定一个aim&#xff0c;代表要找的钱数&#xff0c;求组成aim的最少货币数。…

codeforces gym100959 I - Robots(稠密图建图优化)

I - Robots 显然可以两点之间能连边就连边&#xff0c;但是边数会很多&#xff0c;考虑优化 对于三个点(x0,y0)(x_0,y_0)(x0​,y0​)&#xff0c;(x0,y1)(x_0,y_1)(x0​,y1​)&#xff0c;(x0,y2)(x_0,y_2)(x0​,y2​) 如果三个点的方向都是UUU 那么没有必要1→31\to 31→3连…

.Net Core微服务系列--开篇

得原来有个项目是用wcf做的分布式&#xff0c;不仅横向根据业务拆分了&#xff0c;纵向把业务处理、数据访问等也拆分了成不同的服务&#xff0c;这个是当时公司的产品我也只是一个小小的开发人员所以就不做太多的评论&#xff0c;只是不得不吐槽下调试真的太麻烦。后来&#x…

牛客题霸 [ 树的直径] C++题解/答案

牛客题霸 [ 树的直径] C题解/答案 题目描述 给定一棵树&#xff0c;求出这棵树的直径&#xff0c;即两个节点距离的最大值。 题解&#xff1a; 不知道大家听没听过一个结论&#xff1a; 树的直径可以通过两边dfs找到 步骤&#xff1a; 1.从任意一点进行dfs&#xff0c;然后…

GDOI2021划水记

Day0 上午有意志行&#xff0c;一大早就醒了&#xff0c;然后走了五个小时脚痛。中午洗澡&#xff0c;宿舍轮流看巨人最终话然后聊了一个小时&#xff1f; 下午老师带着我和全爷先开溜&#xff0c;宿舍好像很破旧还还没得充电&#xff0c;领了牌牌和斐爷去吃饭。 然后六点多和…

codeforces1493 D. GCD of an Array(数论)

昨天晚上用的镜像&#xff0c;看的B的图片瞬间不想写了&#xff08;而且这周作业还没碰&#xff09;&#xff0c;不过看到D题突然想做做&#xff0c;于是有了下面的思路&#xff0c;写了一个小时&#xff0c;写完没交看了下榜单发现C题竟然过的人也不多&#xff0c;看了看C题感…

被低估的.net(中) - 广州.net俱乐部2019年纲领

这是被低估的.net系列的中篇。上篇在这里&#xff1a;被低估的.net(上) - 微软MonkeyFest 2018广州分享会活动回顾中篇本来不是这样的&#xff0c;中篇的草稿大纲其实在写上篇之前就写好了&#xff0c;嗯&#xff0c;当时给张队长看过了。然而却因为被.net 粉丝的热情震惊和感动…

P7518-[省选联考2021A/B卷]宝石【主席树,二分】

正题 题目链接:https://www.luogu.com.cn/problem/P7518 题目大意 给出nnn个点的一棵树&#xff0c;每个点上有不大于mmm的数字。 然后给出一个长度为ccc的各个位数不同的序列&#xff0c;每次询问一条路径上找到一个最大的kkk使得该序列的存在1∼k1\sim k1∼k的子序列。 1≤…

牛客题霸 [ 缺失数字] C++题解/答案

牛客题霸 [ 缺失数字] C题解/答案 题目描述 从0,1,2,…,n这n1个数中选择n个数&#xff0c;找出这n个数中缺失的那个数&#xff0c;要求O(n)尽可能小。 题解&#xff1a; 我们可以用map来标记已出现过的数字 因为数组长度给出是len&#xff0c;因为是连续的数字&#xff0c;…

bzoj 2908. 又是nand(树链剖分+区间NAND+单点修改)

首先考虑问题的简化版 存在下面两个操作 询问[l,r][l,r][l,r]区间与非的值即alNANDal1NAND…NANDara_l \text{NAND} a_{l1} \text{NAND}\dots \text{NAND} a_ral​NANDal1​NAND…NANDar​单线修改p,xp,xp,x即apxa_pxap​x 这是一道去年校赛题最近才发现区间与非的板子题 首…

[译]RabbitMQ教程C#版 - 远程过程调用(RPC)

先决条件本教程假定 RabbitMQ 已经安装&#xff0c;并运行在localhost标准端口&#xff08;5672&#xff09;。如果你使用不同的主机、端口或证书&#xff0c;则需要调整连接设置。从哪里获得帮助如果您在阅读本教程时遇到困难&#xff0c;可以通过邮件列表 联系我们。在第 教程…

P7514-[省选联考2021A/B卷]卡牌游戏【贪心】

正题 题目链接:https://www.luogu.com.cn/problem/P7514 题目大意 给出nnn个卡牌有ai/bia_i/b_iai​/bi​&#xff0c;开始都是aia_iai​朝上&#xff0c;将不超过mmm张卡牌变为bib_ibi​面朝上&#xff0c;使得朝上的数字中最大值减去最小值最小。 3≤n≤106,1≤m<n,1≤a…