.NetCore登入功能生成验证码

生成验证码

每一个.NetCore Web项目必不可少的就是登入注册功能,通常登入界面都会有一张验证码图片,只有根据图片输入验证码才能登入成功。验证码的主要功能就是验证操作者是人而非机器人,目的就是防止机器人暴力破解。

1、随机验证码

生成验证码,首先验证码的内容肯定是随机的。每次生成的内容都不相同。

.NetCore随机生成验证码代码示例如下:

string Letters = "1,2,3,4,5,6,7,8,9,0,A,B,C,D,E,F,G,H,I,H,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z";
var array = Letters.Split(",");
var random = new Random();
string code= "";
for(int i = 0; i < 4; i++)
{
    code+= array[random.Next(array.Length)];
}

以上代码是生成字母与数字随机组合的4位验证码,使用的就是Random随机数。

2、验证码画图

获取到随机生成的验证码内容后,就需要根据验证码内容生成图片。而.NetCore数据画图有三种方式。
System.Drawing.Common、SkiaSharp以及ImageSharp

2.1、System.Drawing.Common

使用System.Drawing.Common数据画图代码如下

public byte[] GetCodeImage(string captchaCode)
        {
            //验证码颜色集合
            Color[] c = {Color.MediumAquamarine,Color.MediumBlue,Color.MediumOrchid,Color.MediumPurple,Color.MediumSeaGreen,Color.MediumSlateBlue,
            Color.MediumSpringGreen,Color.Maroon,Color.MediumTurquoise,Color.MidnightBlue,Color.MintCream,Color.MistyRose,
            Color.Moccasin,Color.NavajoWhite,Color.Navy,Color.OldLace,Color.MediumVioletRed,Color.Magenta,Color.Linen,Color.LimeGreen,
            Color.LavenderBlush,Color.LawnGreen,Color.LemonChiffon,Color.LightBlue,Color.LightCoral,Color.LightGoldenrodYellow,
            Color.LightGreen,Color.LightPink,Color.LightSalmon,Color.LightSeaGreen,Color.LightSkyBlue,Color.LightSteelBlue,Color.LightYellow,
            Color.Lime,Color.Olive,Color.OliveDrab,Color.Orange,Color.OrangeRed,Color.Silver,Color.SkyBlue,Color.SlateBlue,
            Color.Snow,Color.SpringGreen,Color.SteelBlue,Color.Tan,Color.Teal,Color.Thistle,Color.Tomato,Color.Transparent,
            Color.Turquoise,Color.Violet,Color.Wheat,Color.White,Color.WhiteSmoke,Color.Sienna,Color.Lavender,Color.SeaShell,
            Color.SandyBrown,Color.Orchid,Color.PaleGoldenrod,Color.PaleGreen,Color.PaleTurquoise,Color.PaleVioletRed,Color.PapayaWhip,
            Color.PeachPuff,Color.Peru,Color.Pink,Color.Plum,Color.PowderBlue,Color.Purple,Color.Red,Color.RosyBrown,Color.RoyalBlue,Color.SaddleBrown,
            Color.Salmon,Color.SeaGreen,Color.Yellow,Color.Khaki,Color.Cyan,Color.DarkMagenta,Color.DarkKhaki,Color.DarkGreen,Color.DarkGoldenrod,
            Color.DarkCyan,Color.DarkBlue,Color.Ivory,Color.Crimson,Color.Cornsilk,Color.CornflowerBlue,Color.Coral,Color.Chocolate,
            Color.DarkOliveGreen,Color.Chartreuse,Color.BurlyWood,Color.Brown,Color.BlueViolet,Color.Blue,Color.BlanchedAlmond,
            Color.Black,Color.Bisque,Color.Beige,Color.Azure,Color.Aquamarine,Color.Aqua,Color.AntiqueWhite,Color.AliceBlue,Color.CadetBlue,
            Color.DarkOrange,Color.YellowGreen,Color.DarkRed,Color.Indigo,Color.IndianRed,Color.DarkOrchid,Color.Honeydew,Color.GreenYellow,
            Color.Green,Color.Goldenrod,Color.Gold,Color.GhostWhite,Color.Gainsboro,Color.Fuchsia,Color.ForestGreen,Color.HotPink,Color.Firebrick,
            Color.FloralWhite,Color.DodgerBlue,Color.DeepSkyBlue,Color.DeepPink,Color.DarkViolet,Color.DarkTurquoise,Color.DarkSlateBlue,
            Color.DarkSeaGreen,Color.DarkSalmon};

            //验证码字体集合
            string[] fonts = { "Verdana", "Microsoft Sans Serif", "Comic Sans MS", "Arial" };

            //定义图像的大小,生成图像的实例
            var image = new Bitmap(140, 50);
			// 画图
            var g = Graphics.FromImage(image);
            // 背景色
            g.Clear(Color.LightGray);
            // 随机
            var random = new Random();
			// 干扰
            for (var i = 0; i < 6; i++)
            {
            	// 干扰点
                //var x = random.Next(image.Width);
                //var y = random.Next(image.Height);
                //g.DrawRectangle(new Pen(Color.LightGray, 0), x, y, 1, 1);
                
                // 干扰线
                g.DrawLine(new Pen(c[random.Next(c.Length)], 4), new Point(random.Next(140), random.Next(50)), new Point(random.Next(140), random.Next(50)));
            }

            //验证码绘制在g中
            for (var i = 0; i < captchaCode.Length; i++)
            {
                //随机颜色索引值
                var cindex = random.Next(c.Length);

                //随机字体索引值
                var findex = random.Next(fonts.Length);

                //字体
                var f = new Font(fonts[findex], 30, FontStyle.Italic);

                //颜色  
                Brush b = new SolidBrush(c[cindex]);
                //绘制一个验证字符  
                g.DrawString(captchaCode.Substring(i, 1), f, b, i * 32 + 1, 1);
            }

            var ms = new MemoryStream();
            image.Save(ms, ImageFormat.Png);

            g.Dispose();
            image.Dispose();
            return ms.ToArray();
        }

以上代码中captchaCode为随机验证码变量。返回的结果为图片Byte数组。

2.2、SkiaSharp

使用SkiaSharp数据画图代码如下:

        public byte[] GetCodeImage(string captchaText, int width = 140, int height = 50, int lineNum = 4, int lineStrookeWidth = 2)
        {
        	// 颜色集合
        	List<SKColor> colors = new List<SKColor>();
            colors.Add(SKColors.AliceBlue);
            colors.Add(SKColors.PaleGreen);
            colors.Add(SKColors.PaleGoldenrod);
            colors.Add(SKColors.Orchid);
            colors.Add(SKColors.OrangeRed);
            colors.Add(SKColors.Orange);
            colors.Add(SKColors.OliveDrab);
            colors.Add(SKColors.Olive);
            colors.Add(SKColors.OldLace);
            colors.Add(SKColors.Navy);
            colors.Add(SKColors.NavajoWhite);
            colors.Add(SKColors.Moccasin);
            colors.Add(SKColors.MistyRose);
            colors.Add(SKColors.MintCream);
            colors.Add(SKColors.MidnightBlue);
            colors.Add(SKColors.MediumVioletRed);
            colors.Add(SKColors.MediumTurquoise);
            colors.Add(SKColors.MediumSpringGreen);
            colors.Add(SKColors.LightSteelBlue);
            colors.Add(SKColors.LightYellow);
            colors.Add(SKColors.Lime);
            colors.Add(SKColors.LimeGreen);
            colors.Add(SKColors.Linen);
            colors.Add(SKColors.PaleTurquoise);
            colors.Add(SKColors.Magenta);
            colors.Add(SKColors.MediumAquamarine);
            colors.Add(SKColors.MediumBlue);
            colors.Add(SKColors.MediumOrchid);
            colors.Add(SKColors.MediumPurple);
            colors.Add(SKColors.MediumSeaGreen);
            colors.Add(SKColors.MediumSlateBlue);
            colors.Add(SKColors.Maroon);
            colors.Add(SKColors.PaleVioletRed);
            colors.Add(SKColors.PapayaWhip);
            colors.Add(SKColors.PeachPuff);
            colors.Add(SKColors.Snow);
            colors.Add(SKColors.SpringGreen);
            colors.Add(SKColors.SteelBlue);
            colors.Add(SKColors.Tan);
            colors.Add(SKColors.Teal);
            colors.Add(SKColors.Thistle);
            colors.Add(SKColors.Tomato);
            colors.Add(SKColors.Violet);
            colors.Add(SKColors.Wheat);
            colors.Add(SKColors.White);
            colors.Add(SKColors.WhiteSmoke);
            colors.Add(SKColors.Yellow);
            colors.Add(SKColors.YellowGreen);
            colors.Add(SKColors.Turquoise);
            colors.Add(SKColors.LightSkyBlue);
            colors.Add(SKColors.SlateBlue);
            colors.Add(SKColors.Silver);
            colors.Add(SKColors.Peru);
            colors.Add(SKColors.Pink);
            colors.Add(SKColors.Plum);
            colors.Add(SKColors.PowderBlue);
            colors.Add(SKColors.Purple);
            colors.Add(SKColors.Red);
            colors.Add(SKColors.SkyBlue);
            colors.Add(SKColors.RosyBrown);
            colors.Add(SKColors.SaddleBrown);
            colors.Add(SKColors.Salmon);
            colors.Add(SKColors.SandyBrown);
            colors.Add(SKColors.SeaGreen);
            colors.Add(SKColors.SeaShell);
            colors.Add(SKColors.Sienna);
            colors.Add(SKColors.RoyalBlue);
            colors.Add(SKColors.LightSeaGreen);
            colors.Add(SKColors.LightSalmon);
            colors.Add(SKColors.LightPink);
            colors.Add(SKColors.Crimson);
            colors.Add(SKColors.Cyan);
            colors.Add(SKColors.DarkBlue);
            colors.Add(SKColors.DarkCyan);
            colors.Add(SKColors.DarkGoldenrod);
            colors.Add(SKColors.DarkGray);
            colors.Add(SKColors.Cornsilk);
            colors.Add(SKColors.DarkGreen);
            colors.Add(SKColors.DarkMagenta);
            colors.Add(SKColors.DarkOliveGreen);
            colors.Add(SKColors.DarkOrange);
            colors.Add(SKColors.DarkOrchid);
            colors.Add(SKColors.DarkRed);
            colors.Add(SKColors.DarkSalmon);
            colors.Add(SKColors.DarkKhaki);
            colors.Add(SKColors.DarkSeaGreen);
            colors.Add(SKColors.CornflowerBlue);
            colors.Add(SKColors.Chocolate);
            colors.Add(SKColors.AntiqueWhite);
            colors.Add(SKColors.Aqua);
            colors.Add(SKColors.Aquamarine);
            colors.Add(SKColors.Azure);
            colors.Add(SKColors.Beige);
            colors.Add(SKColors.Bisque);
            colors.Add(SKColors.Coral);
            colors.Add(SKColors.Black);
            colors.Add(SKColors.Blue);
            colors.Add(SKColors.BlueViolet);
            colors.Add(SKColors.Brown);
            colors.Add(SKColors.BurlyWood);
            colors.Add(SKColors.CadetBlue);
            colors.Add(SKColors.Chartreuse);
            colors.Add(SKColors.BlanchedAlmond);
            colors.Add(SKColors.Transparent);
            colors.Add(SKColors.DarkSlateBlue);
            colors.Add(SKColors.DarkTurquoise);
            colors.Add(SKColors.IndianRed);
            colors.Add(SKColors.Indigo);
            colors.Add(SKColors.Ivory);
            colors.Add(SKColors.Khaki);
            colors.Add(SKColors.Lavender);
            colors.Add(SKColors.LavenderBlush);
            colors.Add(SKColors.HotPink);
            colors.Add(SKColors.LawnGreen);
            colors.Add(SKColors.LightBlue);
            colors.Add(SKColors.LightCoral);
            colors.Add(SKColors.LightCyan);
            colors.Add(SKColors.LightGoldenrodYellow);
            colors.Add(SKColors.LightGreen);
            colors.Add(SKColors.LemonChiffon);
            colors.Add(SKColors.Honeydew);
            colors.Add(SKColors.Green);
            colors.Add(SKColors.DarkViolet);
            colors.Add(SKColors.DeepPink);
            colors.Add(SKColors.DeepSkyBlue);
            colors.Add(SKColors.DimGray);
            colors.Add(SKColors.DodgerBlue);
            colors.Add(SKColors.Firebrick);
            colors.Add(SKColors.GreenYellow);
            colors.Add(SKColors.FloralWhite);
            colors.Add(SKColors.Fuchsia);
            colors.Add(SKColors.Gainsboro);
            colors.Add(SKColors.GhostWhite);
            colors.Add(SKColors.Gold);
            colors.Add(SKColors.Goldenrod);
            colors.Add(SKColors.ForestGreen);
            //创建bitmap位图
            using (SKBitmap image2d = new SKBitmap(width, height, SKColorType.Bgra8888, SKAlphaType.Premul))
            {
                //创建画笔
                using (SKCanvas canvas = new SKCanvas(image2d))
                {
                    //填充背景颜色
                    canvas.DrawColor(SKColors.LightGray);
                    var array = captchaText.ToArray();
                    Random random = new Random();
                    for(int i = 0; i < array.Length; i++)
                    {
                        //将文字写到画布上
                        using (SKPaint drawStyle = CreatePaint(random.Next(colors.Count), height))
                        {
                            canvas.DrawText(array[i].ToString(), i*32+1, height - 1, drawStyle);
                        }
                    }
                    //画随机干扰线
                    using (SKPaint drawStyle = new SKPaint())
                    {

                        for (int i = 0; i < lineNum; i++)
                        {
                            drawStyle.Color = colors[random.Next(colors.Count)];
                            drawStyle.StrokeWidth = lineStrookeWidth;
                            canvas.DrawLine(random.Next(0, width), random.Next(0, height), random.Next(0, width), random.Next(0, height), drawStyle);
                        }
                    }
                    //返回图片byte
                    using (SKImage img = SKImage.FromBitmap(image2d))
                    {
                        using (SKData p = img.Encode(SKEncodedImageFormat.Jpeg, 100))
                        {
                            return p.ToArray();
                        }
                    }
                }
            }
        }
		// 创建画笔
		private SKPaint CreatePaint(int colorNo, float fontSize)
        {
            SKTypeface font = SKTypeface.FromFamilyName("微软雅黑", 16, 16, SKFontStyleSlant.Italic);
            SKPaint paint = new SKPaint();
            paint.IsAntialias = true;
            paint.Color = colors[colorNo];
            paint.Typeface = font;
            paint.TextSize = fontSize;
            return paint;
        }

SkiaSharp代码与System.Drawing.Common逻辑上一致,所以代码很相似。

2.3、ImageSharp

ImageSharp与SkiaShar一致,使用一个即可。

3、保存与返回

获取到图片的Byte数组后,随机生成一个Key,将Key与验证码内容保存至Redis中。再将Key与Byte数组返回致前端。

随机生成Key可使用当前时间戳与随机字符结合。不重复即可。Guid也可
存入Redis可设置一个有效期,建议有效期单位为分钟。

存入Redis参考代码如下:

			// 从配置文件中读取Redis连接字符串
			RedisHelper redisHelper = new RedisHelper(ConfigHelper.GetConfig("RedisConn"));
			// 设置redis数据库
            redisHelper.SetDb(2);
            // 生成Key
            DateTimeOffset dateTimeOffset = new DateTimeOffset(DateTime.Now);
            long mills = dateTimeOffset.ToUnixTimeMilliseconds();
            Random random = new Random();
            string indeCode = "Identity" + mills + random.Next(10) + random.Next(10) + random.Next(10) + random.Next(10);
            // 存入Redis 有效期5分钟
            bool flag = redisHelper.SetValueMinute(indeCode, code, 5);
            if (flag)
            {
                return indeCode;
            }
            else
            {
                throw new Exception("存入Redis失败");
            }
			

RedisHelper代码:

	public class RedisHelper
    {
        private ConnectionMultiplexer redis { get; set; }
        private IDatabase db { get; set; }
        public RedisHelper(string connection)
        {
            redis = ConnectionMultiplexer.Connect(connection);
        }
        /// <summary>
        /// 修改redis数据库
        /// </summary>
        /// <param name="i"></param>
        public void SetDb(Int32 i)
        {
            db = redis.GetDatabase(i);
        }
        /// <summary>
        /// 保存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool SetValue(string key, string value)
        {
            return db.StringSet(key, value);
        }
        /// <summary>
        /// 保存且附带到期时间
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="min"></param>
        /// <returns></returns>
        public bool SetValueMinute(string key,string value,int min)
        {
            return db.StringSet(key, value, new TimeSpan(TimeSpan.TicksPerMinute * min));
        }
        /// <summary>
        /// 获取
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public string GetValue(string key)
        {
            return db.StringGet(key);
        }
        /// <summary>
        /// 删除
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public bool DeleteKey(string key)
        {
            return db.KeyDelete(key);
        }
	}

4、登入验证

登入时前端需将Key与前端输入的验证码都传入登入接口,登入接口需从Redis中根据Key查询相应的验证码内容,检查输入的验证码与对应的验证码内容是否匹配,只有匹配才可进行接下来的登入操作。

使用ReidsHelper中的GetValue方法即可。

5、所遇问题

System.Drawing.Common与SkiaSharp所写的代码,本地调试均可成功。

本地调试结果如下:
本地调试结果
转图片如下:
转图片
而将项目部署至Linux的Docker中,则都会出现问题。SkiaSharp所遇问题为之有干扰线,没有验证码内容。
在这里插入图片描述

System.Drawing.Common所遇问题是:
The type initializer for ‘Gdip‘ threw an exception

网上讲解:SkiaSharp遇到的问题是DrawText方法,将Text内容画到图片中,因为设置的Text字体是Windows中的字体,Linux中没有该字体。所以无法将验证码写入图片中。解决办法就是将Windows中的字体导入Linux中,即可。

而System.Drawing.Common遇到的报错原因是System.Drawing.Common是windows版本的动态库,导入该包时也有提醒。该依赖包只可在Windows中使用。解决方案就是在Linux中下载libgdiplus即可。

以上方案均尝试,无法解决对应的问题。
可能因为网络教程的.NetCore版本是3.1。而我使用的版本为5.0。(仅猜测)

而使用System.Drawing.Common的解决方案,可以解决SkiaSharp的问题。因为libgdiplus是附带字体与语言的。

6、部署方案

因为是使用Docker部署的,每次部署都会生成新的容器,而新容器中又没有libgdiplus。所有又需要重新下载libgdiplus。

只需项目运行依赖的netcore镜像中包含libgdiplus即可解决该问题,如何生成一个附带libgdiplus的镜像包。

操作步骤如下:

首先导入一个对应的Netcore基础镜像包。版本3.1或者5.0自定义

docker pull mcr.microsoft.com/dotnet/aspnet:5.0
docker pull mcr.microsoft.com/dotnet/core/aspnet:3.1

镜像
之后运行进入容器内部

docker run -it mcr.microsoft.com/dotnet/aspnet:5.0 /bin/bash
docker run -it mcr.microsoft.com/dotnet/core/aspnet:3.1 /bin/bash

进入容器后使用命令下载libgdiplus,该操作比较耗时

apt-get update -y
apt-get install -y libgdiplus
apt-get clean
ln -s /usr/lib/libgdiplus.so /usr/lib/gdiplus.dll

下载完成后退出容器,使用docker ps -a查看容器ID。根据容器DI生成镜像

docker commit -a="simple" -m="added libgdiplus based on .netcore5.0" 容器ID dotnetcore-with-libgdiplus:v5.0

docker commit -a="simple" -m="added libgdiplus based on .netcore3.1" 容器ID dotnetcore-with-libgdiplus:v3.1

生成镜像后修改项目的DockerFile文件 即可

FROM dotnetcore-with-libgdiplus:v5.0 AS base
FROM dotnetcore-with-libgdiplus:v3.1 AS base
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值