点击蓝字

关注我

使用验证码保护网站免受垃圾信息的选择有很多,比如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());

}

ASP.NET Core 生成验证码相关推荐

  1. 【转】asp.net mvc生成验证码

    ASP.NET MVC实现网站验证码功能 网站添加验证码,主要为防止机器人程序批量注册,或对特定的注册用户用特定程序暴力破解方式,以进行不断的登录.灌水等危害网站的操作.验证码被广泛应用在注册.登录. ...

  2. [转]asp.net(c#)生成验证码 点击可刷新

    先建一张生成验证码的网页CheckCode.aspx: CheckCode.aspx.cs代码如下 添加引用: using System.IO; using System.Drawing; using ...

  3. [Hei.Captcha] Asp.Net Core 跨平台验证码实现

     写在前面 说起来比较丢脸.我们有个手机的验证码发送逻辑需要使用验证码,这块本来项目里面就有验证码绘制逻辑,.Net Framework的,使用的包是System.Drawing,我把这验证码绘制 ...

  4. Asp.Net 动态生成验证码

    我们在设计用户登录模块时,经常会用到验证码,可以有效地防止黑客软件的恶意破解,现公开我常用的验证码的源代码,生成效果如图:   . 使用方法:          1.在Web项目中添加一个类,如&qu ...

  5. [转]【无私分享:ASP.NET CORE 项目实战(第十四章)】图形验证码的实现

    本文转自:http://www.cnblogs.com/yuangang/p/6000460.html 目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 很长时间没有来更新博客 ...

  6. Asp.Net Core在线生成二维码

    前言: 原先用zxing Code写过基于Winfrom的批量生成二维码工具,以及单个生成二维码工具:批量生成二维码Gihub源代码 今天尝试用QRCoder 加 Asp.Net Core 写了一个在 ...

  7. ASP .NET Core Web Razor Pages系列教程三:自动生成Razor Pages (CRUD)

    系列文章目录:系列教程:使用ASP.NET Core创建Razor Pages Web应用程序 - zhangpeterx的博客 系列教程代码的GitHub地址:ASP .Net Core Razor ...

  8. .net core 生成html,ASP.NET Core 中如何将 .cshtml 视图文件生成为 .cs 文件

    用的是 ASP.NET Core 3.0 经过折腾终于通过下面的代码将 .cshtml 生成了 .cs ,但生成的 .cs 文件无法正常使用 class Program { static void M ...

  9. ASP.NET Core 跨平台图形验证码实现

    概述 几年前,大部分网站.论坛之类的是没有验证码的,因为对于一般用户来说验证码只是增加了用户的操作,降低了用户的体验.但是后来各种灌水机器人.投票机器人.恶意注册机器人层出不穷,大大增加了网站的负担同 ...

最新文章

  1. 36岁前百度员工哀叹:结婚三年老婆要离婚,要求分走大半财产,并且带走孩子。阴差阳错之下,发现孩子不是自己亲生的!...
  2. 【拾贝】版本控制-git 建立Team项目
  3. Springboot 整合 Dubbo/ZooKeeper 详解 SOA 案例
  4. 骑士人才linux伪静态,骑士人才CMS伪静态规则
  5. linux配置python环境变量_linux添加PYTHONPATH环境变量
  6. 【渝粤题库】陕西师范大学300011 历史文献学
  7. 实战:分布式锁详解与代码
  8. No Need(AtCoder-2346)
  9. 移动硬盘显示要格式化怎么办?
  10. pytest系列教程——4、fixture详解
  11. JZOJ4722. 跳楼机
  12. 随机森林——股票涨跌预测模型搭建
  13. QQ抢车位助手(结尾)
  14. 波士顿大学计算机与传媒专业,波士顿大学传媒专业排名如何?
  15. 百度BAE部署java web(Jforum)项目
  16. 青春、情动、永恒的经典——岩井俊二
  17. 判定被7整除的简易方法
  18. 期货交易应该如何界定交易中的时间周期?
  19. 汉诺塔(hanoi)
  20. SQL Server 2008 R2 完全卸载与重新安装

热门文章

  1. piwik抓取用户交互行为
  2. 二进制安装mariadb-10.2.8
  3. 移动端web开发整理
  4. Java类的继承总结
  5. Linux下查看进程对应的命令绝对路径
  6. Shell练习题(持续更新)
  7. struts.properties属性解释
  8. 叮,您有一份ML.NET 速查手册请查收!
  9. C# 11 预览,又增加了实用的语法糖
  10. 【招聘(北京武汉)】北京高远华信科技 .NET 高级工程师