基于ImageSharp实现趣味横生的点选验证

news2024/12/23 0:18:13

引言

  • 随着安全需求的不断提升,传统的文本验证码已经无法满足防止机器自动识别和攻击的要求。点选式验证码作为一种交互式的验证手段,因其更难被自动化脚本破解而逐渐受到欢迎。利用开源图像处理库SixLabors.ImageSharp来实现点选式验证码功能。

ImageSharp简介

  • ImageSharp介绍 SixLabors.ImageSharp是一个跨平台、无依赖的.NET标准图像处理库,支持多种格式的读写和图像操作,其高性能和丰富的API使得它成为.NET开发者进行图像处理的理想工具。与传统的System.Drawing库相比,ImageSharp具有更高的性能和更灵活的扩展性。

点选验证码原理

  • 点选验证码通常要求用户从一组图片中选择出符合特定条件的图片,如选择包含特定文字或图形的图片。这种验证方式能够有效地阻止自动化脚本的攻击,因为自动化脚本很难模拟人类的视觉和点击操作。

使用ImageSharp点选验证码实现

  • 随机生成点选验证文字
  private readonly static Random _random = new();
  /// <summary>  
  /// 生成随机汉字
  /// </summary>  
  /// <param name="number"></param>  
  /// <returns></returns>  
  private static string GetRandomCode(int number)
  {
      var str = "天地玄黄宇宙洪荒日月盈昃辰宿列张寒来暑往秋收冬藏闰余成岁律吕调阳云腾致雨露结为霜金生丽水玉出昆冈剑号巨阙珠称夜光果珍李柰菜重芥姜海咸河淡鳞潜羽翔龙师火帝鸟官人皇始制文字乃服衣裳推位让国有虞陶唐吊民伐罪周发殷汤坐朝问道垂拱平章爱育黎首臣伏戎羌遐迩体率宾归王";
      char[] str_char_arrary = str.ToArray();
      HashSet<string> hs = new HashSet<string>();
      bool randomBool = true;
      while (randomBool)
      {
          if (hs.Count == number)
              break;
          int rand_number = _random.Next(str_char_arrary.Length);
          hs.Add(str_char_arrary[rand_number].ToString());
      }
      string code = string.Join("", hs);
      return code;
  }
  
  • 随机生成点选文字坐标
	
	
	/// <summary>
	/// 获取生成汉子位置随机数
	/// </summary>
	/// <param name="min"></param>
	/// <param name="max"></param>
	/// <returns></returns>
	private static int GetRandom(int min, int max)
	{
	    return _random.Next(min, max);
	}
	   
	/// <summary>
	/// 生成坐标
	/// </summary>
	/// <returns></returns>
	private IList<PointModel> GeneratePoint(int originalWidth, int originalHeight,int codeCount)
	{
	    List<PointModel> points = new List<PointModel>();
	    int paddingNum = 20;
	    var sp = (originalWidth - paddingNum * 2) / codeCount;
	    for (var i = 0; i < codeCount; i++)
	    {
	        var x = GetRandom(i * sp + paddingNum, (i + 1) * sp - paddingNum);
	        var y = GetRandom(paddingNum, originalHeight - paddingNum*2); //留点边距
	        points.Add(new PointModel(x, y));
	    }
	    return points;
	}
    
    //坐标实体类
	public class PointModel
	{
	    /// <summary>
	    /// x坐标
	    /// </summary>
	    public int X { get; set; }
	
	    /// <summary>
	    /// y坐标
	    /// </summary>
	    public int Y { get; set; }
	
	    public PointModel(int x, int y)
	    {
	        X = x;
	        Y = y;
	    }
	}

  • 生成点选验证码返回给前端

/// <summary>
/// 转换为相对于图片的百分比单位
/// </summary>
/// <param name="widthAndHeight">图片宽高</param>
/// <param name="xAndy">相对于图片的绝对尺寸</param>
/// <returns>(int:xPercent, int:yPercent)</returns>
private (int, int) ToPercentPos((int, int) widthAndHeight, (int, int) xAndy)
{
    (int, int) rtnResult = (0, 0);
    // 注意: int / int = int (小数部分会被截断)
    rtnResult.Item1 = (int)(((double)xAndy.Item1) / ((double)widthAndHeight.Item1) * 100);
    rtnResult.Item2 = (int)(((double)xAndy.Item2) / ((double)widthAndHeight.Item2) * 100);

    return rtnResult;
}

 /// <summary>
 /// 生成验证数据
 /// </summary>
 /// <returns>object</returns>
 public async Task<object> GetCaptchaAsync(string captchaKey)
 {
     //    //获取网络图片
     //    //var client = new HttpClient();
     //    //var stream = await client.GetStreamAsync(");
     //    //client.Dispose();
     //    //Bitmap baseImage = new Bitmap(stream);
     //    //stream.Dispose();

     // TODO: 设置答案: 4个字
     int answerLength = 4;
     string imagesDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "captcha", "clickword");
     string[] imagesFiles = Directory.GetFiles(imagesDir.ToPath());
     int imgIndex = _random.Next(imagesFiles.Length);
     string randomImgFile = imagesFiles[imgIndex];
     using var baseImage = await Image.LoadAsync<Rgba32>(randomImgFile.ToPath());
     //重置底图尺寸
     baseImage.Mutate(x =>
     {
         x.Resize(310, 155);
     });
     //字体
     string fontsDir = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "captcha", "fonts");
     string[] fontFiles = new DirectoryInfo(fontsDir.ToPath())?.GetFiles()
         ?.Where(m => m.Extension.ToLower() == ".ttf")
         ?.Select(m => m.FullName).ToArray();

     int baseWidth = baseImage.Width;
     int baseHeight = baseImage.Height;
     //设置字体
     var fontSize = 32;
   
     List<PointModel> data = new List<PointModel>();
     List<int> pointFontList = new List<int>();
     //设置颜色
     Color[] colorArray = { Color.Black, Color.Red, Color.Blue, Color.Green, Color.Orange, Color.Brown, Color.DarkBlue };
     List<char> fontlist = GetRandomCode(6).ToList();
     //随机生成拼图坐标
     IList<PointModel> points = GeneratePoint(baseWidth, baseHeight, fontlist.Count);

     //实际答案
     List<string> wordList = new List<string>();
     for (int i = 0; i < fontlist.Count; i++)
     {
         FontFamily[] families = SystemFonts.Families.ToArray();
         FontFamily fontFamily=new FontFamily();
         if (fontFiles.Length > 0)
         {
             //随机字体
             var randomFont = fontFiles[_random.Next(fontFiles.Length)];
             FontCollection fonts = new FontCollection();
             fontFamily = fonts.Add(randomFont);
         }
         else
         {
             if (families.Length > 0)
             {
                 // 随机选择一个索引  
                 int randomIndex = _random.Next(families.Length);
                 fontFamily = families[randomIndex];
             }
         }
     
         var font = new Font(fontFamily, fontSize, FontStyle.Bold);
         bool isFinish = false;
         int restCount = 0;
         while (!isFinish)
         {
             restCount++;
             if (restCount >= 100 || pointFontList.Count>=6)
                 isFinish = true;
             int fontIndex = _random.Next(points.ToArray().Length);
             if (!pointFontList.Contains(fontIndex))
             {
                 pointFontList.Add(fontIndex);
                 if (data.Count < answerLength)
                 {
                     (int, int) percentPos = ToPercentPos((baseWidth, baseHeight), (points[fontIndex].X, points[fontIndex].Y));
                     // 添加正确答案 位置数据
                     data.Add(new PointModel(percentPos.Item1, percentPos.Item2));
                     wordList.Add(fontlist[i].ToString());
                 }
                 isFinish = true;
                 // 创建文本选项
                 using var fontImage = new Image<Rgba32>(fontSize, fontSize);
                 fontImage.Mutate(ctx => ctx
                         .DrawText(fontlist[i].ToString(), font, colorArray[_random.Next(colorArray.Length)], new PointF(0, 0))
                         .Rotate(_random.Next(-45, 45))
                  );
                 
                 baseImage.Mutate(o =>
                 {
                     //o.DrawText(fontlist[i].ToString(), font, colorArray[random.Next(colorArray.Length)], new PointF(points[fontIndex].X, points[fontIndex].Y));
                     o.DrawImage(fontImage, new Point(points[fontIndex].X, points[fontIndex].Y),1);
            
                 });
             }
         }
     }


     var token = Guid.NewGuid().ToString();
     var captchaData = new
     {
         Token = token,
         Data = new
         {
             BaseImage = baseImage.ToBase64String(PngFormat.Instance),
             PoinText = "请依次点击: " + string.Join(",", wordList)

         }
     };
     var key = string.Format(captchaKey, token);
     await _cache.SetAsync(key, data);

     return captchaData;
 }

  • 服务器端验证点选验证码
/// <summary>
/// 检查验证数据
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<bool> CheckAsync(CaptchaInput input)
{
    if (input == null || input.Data.IsNull())
    {
        return false;
    }
    var key = string.Format(input.CaptchaKey, input.Token);
    if (await _cache.ExistsAsync(key))
    {
        try
        {
            var point = JsonConvert.DeserializeObject<List<PointModel>>(input.Data);
            var pointV = await _cache.GetAsync<List<PointModel>>(key);

            for (int i = 0; i < pointV.Count; i++)
            {
                if (Math.Abs(pointV[i].X - point[i].X) > 10 || Math.Abs(pointV[i].Y - point[i].Y) > 10)
                {
                    await _cache.DelAsync(key);
                    return false;
                }
            }
            if (input.DeleteCache)
            {
                await _cache.DelAsync(key);
            }
            return true;
        }
        catch
        {
            await _cache.DelAsync(key);
            return false;
        }
    }
    else
    {
        return false;
    }
}

最终效果图:
点选验证

参考

  • https://github.com/SixLabors/ImageSharp
  • https://docs.sixlabors.com/

公众号“点滴分享技术猿

关注

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

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

相关文章

将从excel 文件里获取的时间转换成标准时间

new Date(new Date(1900, 0, data.PostingDate).setDate(new Date(1900, 0, data.PostingDate).getDate()-1))

IDA按F5反汇编伪代码错误Please use ida (not ida64) to decompile the current file

IDA不能F5反汇编成为伪代码&#xff0c;提示Warning Please use ida (not ida64) to decompile the current file 不给我转伪代码&#xff0c;不让我用ida64&#xff0c;哪我就用ida32 换32位IDA果然可以了 又可以愉快的玩耍了

2024年1月京东平板电视行业分析:TOP10品牌销量及销额排行榜

鲸参谋监测的京东平台1月份平板电视市场销售数据已出炉&#xff01; 根据鲸参谋电商数据分析平台显示&#xff0c;今年1月份&#xff0c;京东平台上平板电视的销量将近120万件&#xff0c;环比上个月增长约70%&#xff0c;同比去年下滑43%&#xff1b;销售额约34亿元&#xff…

指针的进阶(C语言)(上)

目录 前言 1、字符指针 2、指针数组 3、数组指针 3.1数组指针的定义 3.2 数组名VS&数组名 3.3数组指针的运用 前言 对于指针&#xff0c;我们已经有了初步认识&#xff08;可以看我写的指针详解那一篇文章&#xff09;。 简单总结一下基本概念&#xff1a; 1、指针就…

C# Winfrom实例:武汉智能安检闸机数据接收和解析

项目介绍&#xff1a;本实例主要是接收安检闸机的数据解析并显示到界面上&#xff0c;只做功能实现&#xff0c;不做界面美化 硬件&#xff1a;闸机一个、网线一根、电脑主机开发环境&#xff1a;vs2017 系统&#xff1a;win10涵盖知识点&#xff1a;tcp通讯、文件写入、多线程…

通过Linux终端搭建基于HTTP隧道的文件传输系统

嘿&#xff0c;Linux小侠们&#xff0c;准备好挑战一项酷炫的任务了吗&#xff1f;今天我们要一起通过Linux终端搭建一个基于HTTP隧道的文件传输系统&#xff0c;让我们的文件在网络的海洋中畅游无阻&#xff01; 在开始之前&#xff0c;让我们先来想象一下这个场景&#xff1…

吃个桃桃~

解法&#xff1a;暴力、排序 #include <iostream> #include <vector> #include <algorithm> #include <cmath> using namespace std; #define endl \n struct tree {int index;int peach; }; bool cmp(tree a,tree b) {if (a.peach ! b.peach)return …

提升数据库操作技能:发现MyBatis-Plus学习网站的无限可能!

介绍&#xff1a;MyBatis-Plus是一个对MyBatis框架进行增强的工具&#xff0c;旨在简化开发流程并提升开发效率。以下是MyBatis-Plus的一些主要特点&#xff1a; CRUD操作简化&#xff1a;MyBatis-Plus提供了一些列的CRUD操作方法&#xff0c;这些方法已经封装好&#xff0c;可…

计算机服务器中了devos勒索病毒怎么办?Devos勒索病毒解密数据恢复

网络技术的不断发展与更新&#xff0c;为企业的生产运营提供了有利保障&#xff0c;企业的生产运营离不开数据支撑&#xff0c;通过企业数据可以综合调整发展运营方向&#xff0c;但网络是一把双刃剑&#xff0c;近期&#xff0c;云天数据恢复中心接到许多企业的求助&#xff0…

文件系统创建分区

目录 一、EXT4文件系统创建分区 1.使用 fdisk 分区 2.格式化写入文件系统 3.挂载 临时挂载 永久挂载 二、XFS文件系统创建分区 1.使用 gdisk 分区 2.格式化写入文件系统 3.挂载 临时挂载 永久挂载 三、创建swap分区 1.查看文件系统 2.新建swap分区 3.格式化分区 …

CSP-202006-2-稀疏向量

CSP-202006-2-稀疏向量 【60分思路-暴力枚举】 显然u[100000000], v[100000000]过不了7,8,9,10的样例&#xff0c;但是思路也比较简单易懂&#xff0c;初学者可以参考 #include <iostream> using namespace std; int u[100000000], v[100000000]; long long inner; in…

Java中的关键字

✨✨ 所属专栏&#xff1a; Java基石&#xff1a;深入探索Java核心基础✨✨ &#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; Java中的关键字是一些具有特殊含义的单词&#xff0c;它们在语法中有特定的用途&#xff0c;不能用作标识符&am…

ai写作工具哪具好,3款AI写作软件来揭晓

ai写作工具在当今信息爆炸的时代中正变得越来越受欢迎。随着人工智能技术的不断发展&#xff0c;越来越多的AI写作软件涌现出来&#xff0c;它们的功能和性能也日益完善。在这篇文章中&#xff0c;我们将揭晓3款备受推崇的AI写作软件&#xff0c;探讨它们各自的特点和优势。 第…

网络机顶盒哪个好?内行分享网络机顶盒排名

网络机顶盒是电视机的必备搭配&#xff0c;依然是不可缺少的部分&#xff0c;我作为网络机顶盒的从业者&#xff0c;经常给被问到的就是网络机顶盒哪个好&#xff0c;不同品牌之间差异较大&#xff0c;今天给大家分享业内最新发布的网络机顶盒排名&#xff0c;看看目前哪些网络…

前端简单知识复习

1.symbol类型 Symbol 是 ECMAScript 6 中引入的一种新的基本数据类型&#xff0c;它表示独一无二的值。Symbol 值是通过 Symbol() 函数创建的。 Symbol 值具有以下特点&#xff1a; 独一无二性&#xff08;唯一性&#xff09;&#xff1a;每个通过 Symbol() 函数创建的 Symb…

泛微e-office系统存在敏感信息泄露 附POC软件

免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该文章仅供学习用途使用。 1. 泛微e-office系统简介 微信公众号搜索:南风漏洞复…

阿里云国际-在阿里云服务器上快速搭建幻兽帕鲁多人服务器

幻兽帕鲁是最近流行的新型生存游戏。该游戏一夜之间变得极为流行&#xff0c;同时在线玩家数量达到了200万。然而&#xff0c;幻兽帕鲁的服务器难以应对大量玩家的压力。为解决这一问题&#xff0c;幻兽帕鲁允许玩家建立专用服务器&#xff0c;其提供以下优势&#xff1a; &am…

Ubuntu22部署MySQL5.7详细教程

Ubuntu22部署MySQL5.7详细教程 一、下载MySQL安装包二、安装MySQL三、启动MySQL检查状态登录MySQL 四、开启远程访问功能1、允许其他主机通过root访问数据库2、修改配置文件&#xff0c;允许其他IP通过自定义端口访问 五、使用Navicat连接数据库 默认情况下&#xff0c;Ubuntu2…

Python urllib模块学习

HTTP协议 HTTP 协议&#xff1a;一般指HTTP(超文本传输)协议。 HTTP是为Web浏览器和Web服务器之间的通信而设计的&#xff0c;基于TCP/IP通信协议嘞传递数据。 HTTP消息结构 客户端请求消息 客户端发送一个HTTP请求到服务器的请求消息包括以下格式 请求行(request line)请求…

python毕设选题 - 大数据二手房数据爬取与分析可视化 -python 数据分析 可视化

# 1 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通…