• C# 滑动验证码|拼图验证|SlideCaptcha


    使用背景:

      关于滑动验证码的使用场所还是非常多的,如: 调取短信接口之前,和 注册请求之前 或者 频繁会调用的接口 都需要加这个拼图验证。这里先上一下效果图吧(心中无码,自然高清)。

      话不多说,开撸!

      

     

     

    实现分析:

      滑动验证码的逻辑也很简单。大概说一下:

      1,服务器生成主图+附图(从主图裁剪下来的不需要管y坐标)并且存储X坐标;

      2,前端传入本地X坐标到服务器。

      3,服务器进行计算存储X坐标和本地X坐标相差值;

      4,验证相差值是否在 0-2 之间,判断 true | false

     

        

     

    后端代码:

    准备:

    增加SlideCaptcha文件夹,并且增加Captcha.cs   CaptchaModel.cs   Config.cs   ImgFormat.cs   4个文件。分别是:验证,验证实体,配置和图片生成类。代码如下:

     

    Captcha.cs       

    复制代码
    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Drawing.Imaging;
    using System.IO;
    using System.Net;
    
    
    namespace SliderCaptcha.NET
    {
        public class Captcha
        {
    
            public static Captcha64Model GenerateBase64()
            {
                CaptchaModel model = Captcha.Generate();
                if (model != null)
                {
                    return new Captcha64Model()
                    {
                        X = model.X,
                        Y = model.Y,
                        Background = ImageToBase64(model.Background,ImageFormat.Jpeg),
                        Slide = ImageToBase64(model.Slide, ImageFormat.Png)
                    };
                }
                else
                {
                    return null;
                }
            }
    
            /// <summary>
            /// 生成验证码
            /// </summary>
            /// <returns></returns>
            public static CaptchaModel Generate()
            {
                Bitmap image = BgImage();
                if (image != null)
                {
                    int l = Config.l;
                    int d = Config.d;
                    int width = image.Width;
                    int height = image.Height;
                    int x = RandomNext(width / 3, width - d - l - 10);//初始x
                    int y = RandomNext(10 + d, height - l - 10); ;//初始y
                    GraphicsPath path = GetSliderPath(x, y);
                    Graphics g = GetGraphics(image);
    
                    //水印
                    if (Config.showWatermark)
                    {
                        Font font = new Font("宋体", 12, FontStyle.Bold);
                        SizeF size = g.MeasureString(Config.watermarkText, font);
                        Point Plogo = new Point((int)(width - size.Width - 5), (int)(height - size.Height - 5));
                        Color color = image.GetPixel(Plogo.X, Plogo.Y);
                        SolidBrush bru = new SolidBrush(AntiColor(color));
                        g.DrawString(Config.watermarkText, font, bru, Plogo);
                    }
    
                    Pen pen = new Pen(Color.FromArgb(200, 255, 255, 255), 2);
                    g.DrawPath(pen, path);
                    Image slider = CaptureSlider(image, path, x, width, height);
                    SolidBrush brush = new SolidBrush(Color.FromArgb(100, 255, 255, 255));
                    g.FillPath(brush, path);
                    g.Save();
                    g.Dispose();
                    return new CaptchaModel()
                    {
                        X = x,
                        Y = y,
                        Background = image,
                        Slide = slider
                    };
                }
                return null;
            }
    
    
    
    
            /// <summary>
            /// 获取图片Graphics
            /// </summary>
            /// <param name="image"></param>
            /// <returns></returns>
            private static Graphics GetGraphics(Image image)
            {
                Graphics g = Graphics.FromImage(image);
                g.SmoothingMode = SmoothingMode.HighQuality;
                g.CompositingQuality = CompositingQuality.HighQuality;
                g.InterpolationMode = InterpolationMode.High;
                return g;
            }
    
            /// <summary>
            /// 获取滑块path
            /// </summary>
            private static GraphicsPath GetSliderPath(int x, int y)
            {
                int l = Config.l;
                int r = Config.r;
                int b = Config.b;
                int c = Config.c;
                int d = Config.d;
                int blod = Config.blod;
                GraphicsPath path = new GraphicsPath(FillMode.Winding);
                Point Pa = new Point(x, y);
                Point Pb = new Point(x + l / 2 - b, y - c + blod);
                Point Pd = new Point(x + l, y);
                Point Pe = new Point(Pd.X + c - blod, y + l / 2 - b);
                Point Pg = new Point(Pd.X, y + l);
                Point Ph = new Point(x, y + l);
                Point Pj = new Point(x + c - blod, Pe.Y);
                path.AddLine(Pa, Pb);
                path.AddArc(x + l / 2 - r, y - d, d, d, 130f, 280f);
                path.AddLines(new Point[] { Pd, Pe });
                path.AddArc(x + l, y + l / 2 - r, d, d, 220f, 280f);
                path.AddLines(new Point[] { Pg, Ph });
                path.AddArc(x, y + l / 2 - r, d, d, 140f, -280f);
                path.AddLine(Pj, Pa);
                return path;
            }
    
    
            /// <summary>
            /// 获取滑块区域
            /// </summary>
            /// <param name="image"></param>
            /// <param name="path"></param>
            /// <param name="x"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <returns></returns>
            private static Image CaptureSlider(Image image, GraphicsPath path, int x, int width, int height)
            {
                Bitmap concave = new Bitmap(image.Width, image.Height);
                Graphics g = GetGraphics(concave);
                TextureBrush brush = new TextureBrush(image);
                g.Clear(Color.Transparent);
                g.FillPath(brush, path);
                g.Dispose();
                return CaptureImage(concave, x, height);
            }
    
    
            /// <summary>
            /// 裁剪图片
            /// </summary>
            /// <param name="fromImage"></param>
            /// <param name="offsetX"></param>
            /// <param name="width"></param>
            /// <param name="height"></param>
            /// <returns></returns>
            private static Image CaptureImage(Image fromImage, int offsetX, int height)
            {
                int width = Config.l + Config.d + Config.blod;
                Bitmap bitmap = new Bitmap(width, height);
                Graphics g = GetGraphics(bitmap);
                g.DrawImage(fromImage, 0, 0, new Rectangle(offsetX, 0, width, height), GraphicsUnit.Pixel);
                g.Dispose();
                return bitmap;
            }
    
    
            /// <summary>
            /// 生成随机数
            /// </summary>
            /// <param name="min"></param>
            /// <param name="max"></param>
            /// <returns></returns>
            private static int RandomNext(int min, int max)
            {
                Random random = new Random(Guid.NewGuid().GetHashCode());
                return random.Next(min, max);
            }
    
    
            /// <summary>
            /// 取反色
            /// </summary>
            /// <param name="color"></param>
            /// <returns></returns>
            public static Color AntiColor(Color color)
            {
                if (color.R > 128 && color.G > 128 && color.B > 128)
                {
                    return Color.Black;
                }
                else
                {
                    return Color.White;
                }
    
            }
    
            /// <summary>
            /// 获取背景图
            /// </summary>
            /// <returns></returns>
            private static Bitmap BgImage()
            {
                WebClient web = new WebClient();
                int num = RandomNext(1, 20);
                Stream stream = web.OpenRead($"http://00x1.com/images/Pic/{num}.jpg");
                Bitmap bitmap = (Bitmap)Image.FromStream(stream);
                return bitmap;
            }
    
    
    
            /// <summary>
            /// base64转图片
            /// </summary>
            /// <param name="base64string"></param>
            /// <returns></returns>
            public static Bitmap Base64ToImage(string base64string)
            {
                byte[] b = Convert.FromBase64String(base64string);
                MemoryStream ms = new MemoryStream(b);
                Bitmap bitmap = new Bitmap(ms);
                return bitmap;
            }
    
            /// <summary>
            /// 图片转base64
            /// </summary>
            /// <param name="image"></param>
            /// <returns></returns>
            public static string ImageToBase64(Image image, ImageFormat format)
            {
                if (image == null) return string.Empty;
                string strbaser64 = "";
                try
                {
                    string head = "";
                    string formatName = ImgFormat.NameFromGuid(format);
                    head = $"data:image/{formatName.ToLower()};base64,";
                    MemoryStream ms = new MemoryStream();
                    image.Save(ms, format);
                    byte[] arr = new byte[ms.Length];
                    ms.Position = 0;
                    ms.Read(arr, 0, (int)ms.Length);
                    ms.Close();
                    strbaser64 = head+Convert.ToBase64String(arr);
                }
                catch (Exception)
                {
                    throw new Exception("Something wrong during convert!");
                }
                return strbaser64;
            }
            
        }
    
    }
    复制代码

     

    CaptchaModel.cs     

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    
    namespace SliderCaptcha.NET
    {
        public class CaptchaModel
        {
            public int X { get; set; }
            public int Y { get; set; }
            public Image Background { get; set; }
            public Image Slide { get; set; }
        }
        public class Captcha64Model
        {
            public int X { get; set; }
            public int Y { get; set; }
            public string Background { get; set; }
            public string Slide { get; set; }
        }
    }
    复制代码

          

    Config.cs
    复制代码
    using System;
    
    namespace SliderCaptcha.NET
    {
        public class Config
        {
            /// <summary>
            /// 矩形宽
            /// </summary>
            public static int l = 42;
            /// <summary>
            /// 圆形半径
            /// </summary>
            public static int r = 9;
            /// <summary>
            /// 圆形直径
            /// </summary>
            public static int d = r * 2;
            /// <summary>
            /// 计算圆形与矩形交接三角形边
            /// </summary>
            public static int a = (int)(r * Math.Sin(Math.PI * (50 / 180f)));
            public static int b = (int)(r * Math.Cos(Math.PI * (50 / 180f)));
            public static int c = r - a;
            /// <summary>
            /// 滑块边框
            /// </summary>
            public static int blod = 2;
            /// <summary>
            /// 水印
            /// </summary>
            public static string watermarkText = "SC.NET";
            /// <summary>
            /// 是否显示水印
            /// </summary>
            public static bool showWatermark = true;
    
        }
    }
    复制代码

        

     

    ImgFormat.cs
    复制代码
    using System;
    using System.Collections.Generic;
    using System.Drawing.Imaging;
    
    namespace SliderCaptcha.NET
    {
        public class ImgFormat
        {
    
            private static Dictionary<string, string> Formats = new Dictionary<string, string>() {
                                {"b96b3caa-0728-11d3-9d7b-0000f81ef32e","MemoryBmp"},
                                {"b96b3cab-0728-11d3-9d7b-0000f81ef32e","Bmp"},
                                {"b96b3cac-0728-11d3-9d7b-0000f81ef32e","Emf"},
                                {"b96b3cad-0728-11d3-9d7b-0000f81ef32e","Wmf"},
                                {"b96b3cae-0728-11d3-9d7b-0000f81ef32e","Jpeg"},
                                {"b96b3caf-0728-11d3-9d7b-0000f81ef32e","Png"},
                                {"b96b3cb0-0728-11d3-9d7b-0000f81ef32e","Gif"},
                                {"b96b3cb1-0728-11d3-9d7b-0000f81ef32e","Tiff"},
                                {"b96b3cb2-0728-11d3-9d7b-0000f81ef32e","Exif"},
                                {"b96b3cb5-0728-11d3-9d7b-0000f81ef32e","Icon"}
            };
    
            public static ImageFormat FormatFromGuid(ImageFormat format)
            {
                return FormatFromGuid(format.Guid);
            }
            public static ImageFormat FormatFromGuid(Guid guid)
            {
                return FormatFromGuid(guid.ToString());
            }
            public static ImageFormat FormatFromGuid(string guid)
            {
                if (Formats.ContainsKey(guid))
                {
                    string name = Formats[guid];
                    ImageFormat format = null;
                    switch (name)
                    {
                        case "MemoryBmp":
                            format = ImageFormat.MemoryBmp;
                            break;
                        case "Bmp":
                            format = ImageFormat.Bmp;
                            break;
                        case "Emf":
                            format = ImageFormat.Emf;
                            break;
                        case "Wmf":
                            format = ImageFormat.Wmf;
                            break;
                        case "Gif":
                            format = ImageFormat.Gif;
                            break;
                        case "Jpeg":
                            format = ImageFormat.Jpeg;
                            break;
                        case "Png":
                            format = ImageFormat.Png;
                            break;
                        case "Tiff":
                            format = ImageFormat.Tiff;
                            break;
                        case "Exif":
                            format = ImageFormat.Exif;
                            break;
                        case "Icon":
                            format = ImageFormat.Icon;
                            break;
                    }
                    return format;
                }
                else
                {
                    return null;
                }
                
            }
    
    
    
            public static string NameFromGuid(ImageFormat format)
            {
                return NameFromGuid(format.Guid);
            }
            public static string NameFromGuid(Guid guid)
            {
                return NameFromGuid(guid.ToString());
            }
            public static string NameFromGuid(string guid)
            {
                if (Formats.ContainsKey(guid))
                {
                    return Formats[guid];
                }
                else
                {
                    return string.Empty;
                }
    
            }
    
        }
    }
    复制代码

     

            


    使用:

    这里用的是asp.net mvc 的框架,用的是api接口,前后端分离供前端使用。具体使用根据个人需求,可以是接口调用亦可以是项目调用。

    呼声如果很高的话,考虑出一个winfrom版本的滑动验证码~(滑稽)

    复制代码
            [HttpGet]
            public IHttpActionResult GetCaptcha()
            {
                Captcha64Model model = Captcha.GenerateBase64();
                CacheHelper.Cache.SetCache("sliderX", model.X);
                Hashtable ht = new Hashtable();
                ht.Add("background", model.Background);
                ht.Add("slider", model.Slide);
                ht.Add("sliderXXXXX", model.X);
                return Json(ht);
            }
    
            /// <summary>
            /// 检查验证
            /// </summary>
            /// <param name="x"></param>
            /// <returns></returns>
            [HttpPost]
            public IHttpActionResult CheckCaptcha([FromBody] int x = 0)
            {
                Hashtable hs = new Hashtable();
                string Mess = "";
                int Code = 0;
                var session = CacheHelper.Cache.GetCache("sliderX");
                if (session == null)
                {
                    Mess = "请刷新验证码";
                    Code = 500;
                    goto block;
                }
                string sliderXStr = session?.ToString();// as string
                int sliderX = Convert.ToInt32(sliderXStr);
                int difX = sliderX - x;
                if (difX >= 0 - Config.blod && difX <= Config.blod)
                {
                    Mess = "success";
                    Code = 0;
                }
                else
                {
                    session = null;
                    Mess = "错误";
                    Code = 500;
                }
            block:
                hs.Add("Mess", Mess);
                hs.Add("Code", Code);
                return Json(hs);
            }
    复制代码

     

            

    前端代码:

    复制代码
    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="utf-8">
            <title>滑块验证</title>
            <style>
                * {
                    -webkit-user-select: none;
                    -moz-user-select: none;
                    -ms-user-select: none;
                    user-select: none;
                }
    
                .sc-captcha {
                    width: 300px;
                    margin: 100px auto;
                }
    
                .sc_net_panel {
                    padding: 10px;
                }
    
                .sc_net_panel>div {
                    position: relative;
                }
    
                .bg_slider {
                    position: absolute;
                    left: 0;
                    top: 0;
                }
    
                .bg_refresh {
                    position: absolute;
                    right: 5px;
                    top: 5px;
                    background: #808080;
                    color: #fff;
                    border-radius: 3px;
                    width: 16px;
                    line-height: 16px;
                    text-align: center;
                    cursor: pointer;
                }
    
                .sc_net_slider_icon {
                    position: absolute;
                    left: 0;
                    top: 0;
                    height: 37px;
                    text-align: center;
                    border-radius: 5px;
                    border: #808080 1px solid;
                    width: 37px;
                    line-height: 37px;
                    cursor: pointer;
                }
    
                .sc_net_slider_icon:hover {
                    color: #fff;
                    background: #1991fa;
                }
    
                .sc_net_slider_text {
                    position: absolute;
                    left: 0;
                    top: 0;
                    text-align: center;
                    width: 280px;
                    color: #45494c;
                    border: #808080 1px solid;
                    border-radius: 5px;
                    line-height: 35px;
                    height: 37px;
                    cursor: default;
                }
    
                .sc_net_slider_area {
                    position: absolute;
                    left: 0;
                    top: 0;
                    height: 37px;
                }
            </style>
        </head>
        <body>
            <div>
                <div class="sc-captcha">
                    <div class="sc_net_panel">
                        <div class="sc_net_bg">
                            <div class="sc_net_bgimg">
                            </div>
                            <div class="bg_refresh" onclick="loadCaptcha()" title="刷新">↻</div>
                        </div>
                    </div>
                    <div class="sc_net_panel1">
                        <div class="sc_net_panel  ">
                            <div class="sc_net_slider">
                                <div class="sc_net_slider_text">向右拖动滑块填充拼图</div>
                                <div class="sc_net_slider_area"></div>
                                <div id="table1" class="sc_net_slider_icon">➞</div>
                            </div>
                        </div>
                    </div>
    
                </div>
            </div>
            <script src="http://apps.bdimg.com/libs/jquery/2.1.1/jquery.min.js"></script>
            <script>
                var initial_x; //初始坐标
                var end_x; //结束坐标
                var slider = getByClassName0("sc_net_slider_icon"); //获取滑块
                var bg_slider;
                slider.addEventListener('mousedown', handleDragStart);
                slider.addEventListener('touchstart', handleDragStart);
                document.addEventListener('mousemove', handleDragMove);
                document.addEventListener('touchmove', handleDragMove);
                document.addEventListener('mouseup', handleDragEnd);
                document.addEventListener('touchend', handleDragEnd);
                document.addEventListener('mousedown', function () {
                    return false;
                });
                document.addEventListener('touchstart', function () {
                    return false;
                });
                loadCaptcha();
                //获取图片数据
                function loadCaptcha() {
                    $.ajax({
                        url: "http://192.168.1.216:5500/api/TestApi/GetCaptcha",
                        type: "get",
                        dataType: "JSON",
                        crossDomain: true,
                        success: function (json) {
                            $("#table1").css("position", "absolute");//滑块恢复初始位置
                            $("#table1").css("left", 0);//滑块恢复初始位置
                            isMouseDown = false;//滑块禁止移动
                            var bg = createCanvas(280, 155); //定义画布大小
                            bg.className = 'bg_img'; //定义画布名称
                            bg_slider = createCanvas(62, 155); //定义滑块大小
                            bg_slider.className = 'bg_slider'; //定义滑块名称
                            CanvasSetImage(bg, json.background); //创建画布
                            CanvasSetImage(bg_slider, json.slider); //创建滑块
                            /*向界面添加节点*/
                            var doc = document.getElementsByClassName("sc_net_bgimg")[0];
                            doc.innerHTML = "";
                            doc.appendChild(bg);
                            doc.appendChild(bg_slider);
                            /*end*/
    
                        }
                    })
                }
                //设置画布,滑块的大小
                function createCanvas(width, height) {
                    var canvas = document.createElement('canvas');
                    canvas.width = width;
                    canvas.height = height;
                    return canvas;
                };
                //创建画布,滑块
                function CanvasSetImage(_canvas, base64) {
    
                    //获取2d画布对象
                    var ctx = _canvas.getContext("2d");
                    //创建图片标签
                    var _img = document.createElement("img");
                    //设置图片地址
                    _img.src = base64;
                    _img.onload = function () {
                        ctx.drawImage(_img, 0, 0);
                    }
                }
                //根据类名获取元素
                function getByClassName0(className) {
                    return document.getElementsByClassName(className)[0];
                };
                //originX  originY初始坐标
                //clientX  clientY相对于界面的坐标
                //screenX screenY相对于屏幕的坐标
                //isMouseDown=true/false 滑块是否能移动
                var originX, originY, trail = [],
                    isMouseDown = false;
                var isOk = false;
                //滑块启动
                function handleDragStart(e) {
                    if (isOk) return;
                    originX = e.screenX || e.touches[0].clientX;
                    originY = e.screenY || e.touches[0].clientY;
                    isMouseDown = true;
                    initial_x = $(".bg_slider").offset().left; //获取初始坐标
    
                };
                //滑块拖动
                function handleDragMove(e) {
    
                    if (!isMouseDown) return false;
                    var eventX = e.clientX || e.touches[0].clientX;
                    var eventY = e.clientY || e.touches[0].clientY;
                    var moveX = eventX - originX;
                    var moveY = eventY - originY;
                    if (moveX >= 0 && moveX <= 243) {
                        slider.style.left = moveX + "px";
                        bg_slider.style.left = moveX / 243 * 218 + "px";
                    }
                };
                //滑块结束
                function handleDragEnd(e) {
                    if (!isMouseDown) return false;
                    isMouseDown = false;
                    var eventX = e.clientX || e.changedTouches[0].clientX;
                    if (eventX == originX) return false;
                    end_x = $(".bg_slider").offset().left; //获取最终坐标
                    var result = Math.round(end_x - initial_x);
                    $.post("http://192.168.1.216:5500/api/TestApi/CheckCaptcha?x=" + result, function (res) {
                        if (res.Mess == "错误") {
                            alert("出错了,再试一遍吧!");
                            handleDragStart(e);
                            loadCaptcha();
                        } else {
                            alert("恭喜你,已打败80%的用户!");
                        }
                    });
                };
            </script>
        </body>
    </html>
    复制代码

     

    结语:

        到这里基本上就已经完成了,以上前端地址更换成自己的接口即可。正常业务 验证码是很频发的一个操作并且是多用户。所以缓存存储的时候也需要注意一下。最后有问题的话,可以评论区沟通。感谢观看!(这次很用心在写,能否上个推荐~)

     

  • 相关阅读:
    Spark性能调优之广播变量
    新零售SaaS架构:订单履约系统的概念模型设计
    Mac Mini 安装Ubuntu20.04 KVM
    Doc as Code (4):使用Git做版本管理,而不是使用目录做版本管理
    25 Fisher判别
    LeetCode 27. 移除元素(JavaScript 简单)
    逻辑漏洞(越权)
    vue2知识点:浏览器本地缓存
    【LeetCode】合并石头的最低成本 [H](动态规划)
    Linux操作系统:Firewalld
  • 原文地址:https://www.cnblogs.com/BFMC/p/16172817.html