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

引言

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

公众号“点滴分享技术猿

关注

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

APP 有漏洞被测要下架,怎么处理?

事情的经过是这样的&#xff1a; 1&#xff1a;学员公司测试的 APP 发现有漏洞&#xff0c;被要求下架 2&#xff1a;他被公司要求去查询 APP 哪里有漏洞 3&#xff1a;他来寻求帮助&#xff0c;推荐几款安全测试扫描漏洞的问题。 事情的梳理&#xff1a; 1:我们看了他的 …

UE5 C++ UENUM 和 USTRUCT

一.首先在APawn里声明 UENUM 和 USTRUCT。UENUM 有两种定义方式 一种是使用命名空间&#xff1a; 还有是继承uint8&#xff1a; 通过申明class类 别名来替代 USTRUCT的定义 上面的第二种有类似但仍然有很多的差异&#xff1a; 首先要有GENERATED_USTRUCT_BODY()这个函数 并且…

fastApi笔记04-查询参数和字符串校验

额外校验 使用Query可以对查询参数添加校验 from typing import Unionfrom fastapi import FastAPI, Queryapp FastAPI()app.get("/items/") async def read_items(q: Union[str, None] Query(defaultNone, max_length50)):results {"items": [{"…

春招面试准备笔记——过拟合和欠拟合

介绍 过拟合&#xff1a;过拟合是指模型在训练过程中过于复杂&#xff0c;拥有过多的参数&#xff0c;以至于在训练数据集上表现良好&#xff0c;但在未见过的测试数据上表现很差的现象。这种情况下&#xff0c;模型可能只是“死记硬背”了训练数据的特征和噪声&#xff0c;而…

SpringBoot中使用PageHelper插件实现Mybatis分页

场景 SpringBoot中整合Mybatis时一般添加的依赖为 <dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.1</version></dependency> 如果要实现分页查…

解锁ThreadLocal的问题集:如何规避多线程中的坑

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 解锁ThreadLocal的问题集&#xff1a;如何规避多线程中的坑 前言内存泄露问题内存泄漏原因&#xff1a;检测和避免内存泄漏的实用建议&#xff1a; 线程池带来的数据混乱最佳实践&#xff1a;注意事项…

CVE-2024-0918 TEW-800MB RCE漏洞分析

漏洞描述 固件版本为1.0.1.0的TEW-800MB路由器存在命令注入漏洞。如果攻击者获得了web管理权限&#xff0c;他们可以将命令注入到httpd未知函数中的post请求参数DeviceURL中&#xff0c;从而获得shell权限。。 参考链接 TEW-800MB (notion.site)https://warp-desk-89d.notio…

从宏观到微观——泽攸科技ZEM系列台式扫描电子显微镜在岩石分析中的应用

岩石作为地球地壳的主要构成物质之一&#xff0c;其微观结构对于了解地质过程、资源勘探以及工程建设具有重要意义。按照岩石的成因&#xff0c;可以把它们分为三类&#xff1a;岩浆岩、沉积岩和变质岩。在地球表面&#xff0c;沉积岩占据75%的份额&#xff0c;而在地壳深度&am…

Family Day/园区参观路径(C语言)

题目描述 园区某部门举办了Family Day&#xff0c;邀请员工及其家属参加&#xff1b; 将公司园区视为一个矩形&#xff0c;起始园区设置在左上角&#xff0c;终点园区设置在右下角&#xff1b; 家属参观园区时&#xff0c;只能向右和向下园区前进&#xff0c;求从起始园区到终…

低代码中的工作流:简化开发流程,提升效率

低代码开发平台近年来在软件开发领域引起了广泛的关注和应用。它以提高开发效率、降低开发成本为目标&#xff0c;通过简化开发过程&#xff0c;使非专业开发者也能快速构建高品质的应用程序。其中&#xff0c;工作流引擎作为低代码开发平台的重要组成部分&#xff0c;对于提升…

使用C# Net6连接国产达梦数据库记录

达梦官网&#xff1a;http://www.dameng.com/ 1 下载达梦并进行安装 下载地址&#xff1a;官网首页——服务与合作——下载中心&#xff08;https://www.dameng.com/list_103.html&#xff09; 根据需要自行下载需要的版本&#xff0c;测试版本为&#xff1a;x86 win64 DM8版…

设计师必看!哪个云渲染平台便宜?

渲染100 溜云库 渲云 平均价格 9.27 9.37 9.51 Camera007 5.81 6.1 4.7 Camera008 18.66 17…

【C语言】中的位操作符和移位操作符,原码反码补码以及进制之间的转换

欢迎大家来到c语言知识小课堂&#xff0c;今天的知识点是操作符和进制 目录 一、进制之间的转化1、什么是二进制&#xff0c;八进制&#xff0c;十进制&#xff0c;十六进制2、进制之间的转化其他进制转化为十进制十进制转化为二进制二进制转化为八进制八进制转化为二进制二进…

三维GIS开发的就业前景

一、前言 三维GIS是一个伪概念,GIS是地理信息系统&#xff0c;三维GIS就是三维地理信息系统&#xff0c;在课本上&#xff0c;专业概念上&#xff0c;也没有这一说法吧&#xff0c;所以三维GIS&#xff0c;就是技术人员造概念拼凑造出来的&#xff0c;本质上就是GIS三维可视化…

【学习笔记】数据结构与算法03:栈与队列

知识出处&#xff1a;Hello算法&#xff1a;https://www.hello-algo.com/. 文章目录 2.2 栈和队列2.2.1 「栈 stack」2.2.1.1 栈的常用操作2.2.1.2 栈的典型应用 2.2.2「队列 queue」2.2.2.1 队列的常用操作2.2.2.2 队列的典型应用 2.2.3 双向队列 「double-ended queue」2.2.3…

2024 Impeller:快速了解 Flutter 的渲染引擎的优势

参考原文 &#xff1a;https://tomicriedel.medium.com/understanding-impeller-a-deep-dive-into-flutters-rendering-engine-ba96db0c9614 最近&#xff0c;在 Flutter 2024 路线规划里明确提出了&#xff0c;今年 Flutter Team 将计划删除 iOS 上的 Skia 的支持&#xff0c;…

java异常处理设计

异常的继承体系 java 中的异常的超类是 java.lang.Throwable(后文省略为 Throwable), 他有俩自类Exception和Error&#xff0c;Error是由jvm管理&#xff0c;我们不需要考虑。 RuntimeException是Exception的子类。 检查异常&#xff08;Checked Exceptions&#xff09;&#…

Sparse ICP的使用(一)

一、代码下载以及修改 下载以及建立项目&#xff1a; 链接&#xff1a;palanglois/icpSparse: Implementation of the sparse icp algorithm (github.com) 如果github进不去&#xff0c;我这里下载好了&#xff1a;Sparseicp源码资源-CSDN文库 下载好了之后&#xff0c;会…

【关于python变量类型学习笔记】

python的变量类型 在创建变量时会在内存中开辟一个空间&#xff0c;变量是存储在内存中的值。 根据变量的数据类型&#xff0c;解释器会分配指定内存&#xff0c;并决定什么数据可以被存储在内存中。 变量可以指定不同的数据类型&#xff0c;这些变量可以存储整数&#xff0c;…

Canvas绘制

Canvas绘制 一、介绍效果图 二、画圆1 写一个页面2 画一个圆&#xff08;点&#xff09;3 效果 三 画直线1 写一个页面2 画直线3 效果 四 用直线连接两个点1 写一个页面2 连线3 效果 五 画随机点1 写一个页面2 随机点3 效果 六 画随机点并连线1 写一个页面2 画点连线3 效果 七 …