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

本文介绍了如何使用SixLabors.ImageSharp库在.NET中创建交互式点选验证码,包括随机生成文字和坐标,以及服务器端验证的过程。着重展示了代码实现和验证码的工作原理。
摘要由CSDN通过智能技术生成

引言

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

最终效果图:
点选验证

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

搬砖的工人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值