26 Star 393 Fork 127

pojianbing/LazyCaptcha

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
Loading...
README
MIT

LazyCaptcha v2(基于SkiaSharp)

介绍

仿EasyCaptchaSimpleCaptcha,基于.Net Standard 2.0 的图形验证码模块。 v2是指版本号>=2.0.0的版本,<2.0.0则称为v1。 v1基于ImageSharp,v2基于SkiaSharp。SkiaSharp性能更好,但发布到linux时需要安装对应NativeAssets(ImageSharp则不需要)。 v1文档地址

主要特性

  • 🎨 多种验证码类型:支持数字、字母、中文、算术表达式等多种验证码类型
  • 🎭 丰富的字体选择:内置多种字体,支持自定义字体
  • 🎬 动静态图片:支持静态图片和GIF动画验证码
  • 🛡️ 频率限制:内置验证码生成频率限制功能,防止恶意请求
  • 🔧 高度可配置:支持丰富的配置选项,满足不同场景需求
  • 🚀 高性能:基于SkiaSharp,性能优异
  • 💾 多种存储:支持内存存储和分布式缓存存储

滑动验证码请移步lazy-slide-captcha
码云地址 Github 地址

Deepwiki

在线演示(欠费失效)

效果展示

CaptchaType 字体 静态图 动图
DEFAULT (0) Actionj 输入图片说明 输入图片说明
CHINESE (1) kaiti 输入图片说明 输入图片说明
NUMBER (2) Fresnel 输入图片说明 输入图片说明
NUMBER_ZH_CN (3) kaiti 输入图片说明 输入图片说明
NUMBER_ZH_HK (4) kaiti 输入图片说明 输入图片说明
WORD (5) Epilog 输入图片说明 输入图片说明
WORD_LOWER (6) Epilog 输入图片说明 输入图片说明
WORD_UPPER (7) Epilog 输入图片说明 输入图片说明
WORD_NUMBER_LOWER (8) Epilog 输入图片说明 输入图片说明
WORD_NUMBER_UPPER (9) Epilog 输入图片说明 输入图片说明
ARITHMETIC (10) Epilog 输入图片说明 输入图片说明
ARITHMETIC_ZH (11) kaiti 输入图片说明 输入图片说明
字体 图片 字体 图片
Actionj 输入图片说明 Epilog 输入图片说明
Fresnel 输入图片说明 Headache 输入图片说明
Kaiti 输入图片说明 Lexo 输入图片说明
Prefix 输入图片说明 Progbot 输入图片说明
Ransom 输入图片说明 Robot 输入图片说明
Scandal 输入图片说明

输入图片说明

从2.1.0起图片格式调整为png,可以设置背景色的透明度,例如#00ffffff

安装

Install-Package Lazy.Captcha.Core
dotnet add package Lazy.Captcha.Core

linux环境下运行,请安装SkiaSharp.NativeAssets.Linux.NoDependencies包,更多细节请查看SkiaSharp官方文档。

使用说明

1. 注册服务

// 默认使用内存存储(AddDistributedMemoryCache)
builder.Services.AddCaptcha(builder.Configuration);

// 如果使用redis分布式缓存
//builder.Services.AddStackExchangeRedisCache(options =>
//{
//    options.Configuration = builder.Configuration.GetConnectionString("RedisCache");
//    options.InstanceName = "captcha:";
//});

2. 配置

appsettings.json (不提供配置时,使用默认配置)
{
  "ConnectionStrings": {
    // 使用Redis缓存时,需要配置此项
    // 使用格式参考 Microsoft.Extensions.Caching.StackExchangeRedis
    "RedisCache": "localhost,password=Aa123456."
  },
  "CaptchaOptions": {
    "CaptchaType": 5, // 验证码类型
    "CodeLength": 4, // 验证码长度, 要放在CaptchaType设置后  当类型为算术表达式时,长度代表操作的个数, 例如2
    "ExpirySeconds": 60, // 验证码过期秒数
    "IgnoreCase": true, // 比较时是否忽略大小写
    "StorageKeyPrefix": "", // 存储键前缀
    "RateLimit": {
      "Enabled": false, // 是否启用频率限制
      "WindowSeconds": 60, // 时间窗口(秒)
      "MaxRequests": 5 // 时间窗口内最大请求次数
    },
    "ImageOption": {
      "Animation": false, // 是否启用动画
      "FontSize": 32, // 字体大小
      "Width": 100, // 验证码宽度
      "Height": 40, // 验证码高度
      "BubbleMinRadius": 5, // 气泡最小半径
      "BubbleMaxRadius": 10, // 气泡最大半径
      "BubbleCount": 3, // 气泡数量
      "BubbleThickness": 1.0, // 气泡边沿厚度
      "InterferenceLineCount": 3, // 干扰线数量
      "FontFamily": "kaiti", // 包含actionj,epilog,fresnel,headache,lexo,prefix,progbot,ransom,robot,scandal,kaiti
      "FrameDelay": 15, // 每帧延迟,Animation=true时有效, 默认30
      "BackgroundColor": "#ffffff", //  格式: rgb, rgba, rrggbb, or rrggbbaa format to match web syntax, 默认#fff
      "ForegroundColors": "", //  颜色格式同BackgroundColor,多个颜色逗号分割,随机选取。不填,空值,则使用默认颜色集
      "Quality": 100, // 图片质量(质量越高图片越大,gif调整无效可能会更大)
      "TextBold": false // 粗体,该配置2.0.3新增
    }
  }
}

配置可以通过运行Sample.Winfrom生成或直接下载Release运行。 输入图片说明

代码配置
// 全部配置
builder.Services.AddCaptcha(builder.Configuration, option =>
    option.CaptchaType = CaptchaType.WORD; // 验证码类型
    option.CodeLength = 6; // 验证码长度, 要放在CaptchaType设置后.  当类型为算术表达式时,长度代表操作的个数
    option.ExpirySeconds = 30; // 验证码过期时间
    option.IgnoreCase = true; // 比较时是否忽略大小写
    option.StorageKeyPrefix = ""; // 存储键前缀

    option.ImageOption.Animation = true; // 是否启用动画
    option.ImageOption.FrameDelay = 30; // 每帧延迟,Animation=true时有效, 默认30

    option.ImageOption.Width = 150; // 验证码宽度
    option.ImageOption.Height = 50; // 验证码高度
    option.ImageOption.BackgroundColor = SkiaSharp.SKColors.White; // 验证码背景色

    option.ImageOption.BubbleCount = 2; // 气泡数量
    option.ImageOption.BubbleMinRadius = 5; // 气泡最小半径
    option.ImageOption.BubbleMaxRadius = 15; // 气泡最大半径
    option.ImageOption.BubbleThickness = 1; // 气泡边沿厚度

    option.ImageOption.InterferenceLineCount = 2; // 干扰线数量

    option.ImageOption.FontSize = 36; // 字体大小
    option.ImageOption.FontFamily = DefaultFontFamilys.Actionj; // 字体
    
    /* 
     * 中文使用kaiti,其他字符可根据喜好设置(可能部分转字符会出现绘制不出的情况)。
     * 当验证码类型为“ARITHMETIC”时,不要使用“Ransom”字体。(运算符和等号绘制不出来)
     */

     option.ImageOption.TextBold = true;// 粗体,该配置2.0.3新增
});

3. Controller

[Route("captcha")]
[ApiController]
public class CaptchaController : Controller
{
    private readonly ICaptcha _captcha;

    public CaptchaController(ICaptcha captcha)
    {
        _captcha = captcha;
    }

    [HttpGet]
    public IActionResult Captcha(string id)
    {
        var info = _captcha.Generate(id);
        // 有多处验证码且过期时间不一样,可传第二个参数覆盖默认配置。
        //var info = _captcha.Generate(id,120);
        var stream = new MemoryStream(info.Bytes);
        return File(stream, "image/gif");
    }

    /// <summary>
    /// 演示时使用HttpGet传参方便,这里仅做返回处理
    /// </summary>
    [HttpGet("validate")]
    public bool Validate(string id, string code)
    {
        return _captcha.Validate(id, code);
    }

    /// <summary>
    /// 多次校验(https://gitee.com/pojianbing/lazy-captcha/issues/I4XHGM)
    /// 演示时使用HttpGet传参方便,这里仅做返回处理
    /// </summary>
    [HttpGet("validate2")]
    public bool Validate2(string id, string code)
    {
        return _captcha.Validate(id, code, false);
    }
}

频率限制功能

从 v2.2.0 开始,LazyCaptcha 支持验证码生成频率限制功能,可以有效防止恶意用户频繁请求验证码。

⚠️ 重要说明:要开启 RateLimit,必须同时满足以下两个条件:

  1. 配置开启 - 在配置文件或代码中设置 RateLimit.Enabled = true
  2. 使用正确的方法 - 必须使用 GenerateWithRateLimit() 方法,而不是普通的 Generate() 方法

仅配置开启而继续使用 Generate() 方法,频率限制不会生效!

1. 启用频率限制

配置文件方式
{
  "CaptchaOptions": {
    "RateLimit": {
      "Enabled": true, // 启用频率限制
      "WindowSeconds": 60, // 60秒时间窗口
      "MaxRequests": 5 // 最多5次请求
    }
  }
}
代码配置方式
builder.Services.AddCaptcha(builder.Configuration, option =>
{
    // 启用频率限制
    option.RateLimit.Enabled = true;
    option.RateLimit.WindowSeconds = 60; // 60秒时间窗口
    option.RateLimit.MaxRequests = 3; // 最多3次请求
});

2. 在控制器中使用

🔑 关键点:必须使用 GenerateWithRateLimit() 方法才能应用频率限制!

[Route("captcha")]
[ApiController]
public class CaptchaController : Controller
{
    private readonly ICaptcha _captcha;

    public CaptchaController(ICaptcha captcha)
    {
        _captcha = captcha;
    }

    [HttpGet]
    public IActionResult Captcha(string id)
    {
        try
        {
            // 获取客户端IP地址
            var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString();
            var rateLimitKey = clientIp.ToRateLimitKey();

            // 使用频率限制生成验证码
            var info = _captcha.GenerateWithRateLimit(id, rateLimitKey);
            var stream = new MemoryStream(info.Bytes);
            return File(stream, "image/png");
        }
        catch (RateLimitExceededException ex)
        {
            return StatusCode(429, new
            {
                error = "频率限制超出",
                message = ex.Message,
                remainingRequests = ex.RemainingRequests,
                resetTime = ex.ResetTime,
                windowSeconds = ex.WindowSeconds
            });
        }
    }

    [HttpGet("rate-limit-status")]
    public IActionResult GetRateLimitStatus()
    {
        var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString();
        var rateLimitKey = clientIp.ToRateLimitKey();

        var result = _captcha.CheckRateLimit(rateLimitKey);

        return Ok(new
        {
            isAllowed = result.IsAllowed,
            remainingRequests = result.RemainingRequests,
            resetTime = result.ResetTime,
            secondsUntilReset = result.GetSecondsUntilReset()
        });
    }
}

3. 不同的限制策略

// 基于IP地址限制(默认)
var ipKey = clientIp.ToRateLimitKey();
var captchaData = _captcha.GenerateWithRateLimit(captchaId, ipKey);

// 基于用户ID限制
var userKey = RateLimitKeyStrategy.UserId.GenerateRateLimitKey(userId: userId);
var captchaData = _captcha.GenerateWithRateLimit(captchaId, userKey);

// 自定义键限制
var customKey = RateLimitKeyStrategy.Custom.GenerateRateLimitKey(customKey: deviceId);
var captchaData = _captcha.GenerateWithRateLimit(captchaId, customKey);

4. 特性说明

  • 轻量级设计:基于内存的简单实现,无需外部依赖
  • 灵活配置:支持自定义时间窗口和请求次数限制
  • 多种策略:支持基于IP地址、用户ID或自定义键的限制
  • 优雅降级:可以选择性启用/禁用,不影响现有功能
  • 详细信息:提供剩余请求次数和重置时间信息
  • 线程安全:使用 ConcurrentDictionary 确保线程安全
  • 自动清理:过期条目会自动清理,避免内存泄漏

5. 注意事项

  • ⚠️ 重要:默认情况下频率限制是禁用的,需要显式启用
  • ⚠️ 重要:仅配置 RateLimit.Enabled = true 不够,必须使用 GenerateWithRateLimit() 方法
  • ⚠️ 重要:如果继续使用 Generate() 方法,即使配置了频率限制也不会生效
  • 基于内存的实现在应用重启后会丢失限制状态
  • 在负载均衡环境中,每个实例都有独立的限制计数器
  • 建议在生产环境中根据需要实现基于Redis的分布式限制器

自定义随机验证码

动图和静态图随机出现, CaptchaType随机。

1. 自定义RandomCaptcha

/// <summary>
/// 随机验证码
/// </summary>
public class RandomCaptcha : DefaultCaptcha
{
    private static readonly Random random = new();
    private static readonly CaptchaType[] captchaTypes = Enum.GetValues<CaptchaType>();

    public RandomCaptcha(IOptionsSnapshot<CaptchaOptions> options, IStorage storage) : base(options, storage)
    {
    }

    /// <summary>
    /// 更新选项
    /// </summary>
    /// <param name="options"></param>
    protected override void ChangeOptions(CaptchaOptions options)
    {
        // 随机验证码类型
        options.CaptchaType = captchaTypes[random.Next(0, captchaTypes.Length)];

        // 当是算数运算时,CodeLength是指运算数个数
        if (options.CaptchaType.IsArithmetic())
        {
            options.CodeLength = 2;
        }
        else
        {
            options.CodeLength = 4;
        }

        // 如果包含中文时,使用kaiti字体,否则文字乱码
        if (options.CaptchaType.ContainsChinese())
        {
            options.ImageOption.FontFamily = DefaultFontFamilys.Kaiti;
            options.ImageOption.FontSize = 24;
        }
        else
        {
            options.ImageOption.FontFamily = DefaultFontFamilys.Actionj;
        }

        // 动静随机
        options.ImageOption.Animation = random.Next(2) == 0;

        // 干扰线随机
        options.ImageOption.InterferenceLineCount = random.Next(1, 4);

        // 气泡随机
        options.ImageOption.BubbleCount = random.Next(1, 4);

        // 其他选项...
    }
}

2. 注入RandomCaptcha

// 内存存储, 基于appsettings.json配置
builder.Services.AddCaptcha(builder.Configuration);
// 如果开启随机验证码,请打开下面的注释即可。
// builder.Services.Add(ServiceDescriptor.Scoped<ICaptcha, RandomCaptcha>());

RandomCaptcha不包含在类库内部,仅做自定义演示,您可以根据自己的喜好,随机所有的CaptchaOptions值。

自定义字体

1. 寻找字体

你可以通过fontspace找到自己喜爱的字体。

2. 将字体放入项目,并设置为嵌入资源。

当然也可以不作为嵌入资源,放到特定目录也是可以的,只要对下边ResourceFontFamilysFinder稍作修改即可。

输入图片说明

3. 定义查找字体帮助类,示例使用ResourceFontFamilysFinder

public class ResourceFontFamilysFinder
{
    private static Lazy<List<SKTypeface>> _fontFamilies = new Lazy<List<SKTypeface>>(() =>
    {
        var fontFamilies = new List<SKTypeface>();
        var assembly = Assembly.GetExecutingAssembly();
        var names = assembly.GetManifestResourceNames();

        if (names?.Length > 0 == true)
        {
            foreach (var name in names)
            {
                if (!name.EndsWith("ttf")) continue;
                fontFamilies.Add(SKTypeface.FromStream(assembly.GetManifestResourceStream(name)));
            }
        }

        return fontFamilies;
    });


    public static SKTypeface Find(string name)
    {
        return _fontFamilies.Value.First(e => e.FamilyName == name);
    }
}

4. 设置option

// 内存存储, 基于appsettings.json配置
builder.Services.AddCaptcha(builder.Configuration, options =>
{
    // 自定义字体
    options.ImageOption.FontSize = 28;
    options.ImageOption.FontFamily = ResourceFontFamilysFinder.Find("KG HAPPY"); // 字体的名字在打开ttf文件时会显示
});

.Net Framework下使用

新建mvc项目,.Net Framework选择4.6.2。

1. Nuget安装

先安装SkiaSharp, 再安装Lazy.Captcha.Core

2. Global.asax增加

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
        CaptchaConfig();
    }

    private void CaptchaConfig()
    {
        var captchaService = CaptchaServiceBuilder
            .New()
            .Width(98)
            .Height(35)
            .FontSize(20)
            .CaptchaType(CaptchaType.ARITHMETIC)
            .FontFamily(DefaultFontFamilys.Ransom)
            .InterferenceLineCount(3)
            .Animation(false)
            .Build();
        CaptchaHelper.Initialization(captchaService);
    }
}

3. Controller使用

public class CaptchaController : Controller
{
    [HttpGet]
    public ActionResult Index()
    {
        var id = Guid.NewGuid().ToString().Replace("_", "").Replace("-", "");
        var captchaData = CaptchaHelper.Generate(id);
        var output = new CaptchaResponse
        {
            Id = id,
            Base64 = captchaData.Base64
        };
        return Json(output, JsonRequestBehavior.AllowGet);
        
    }

    /// <summary>
    /// 演示时使用HttpGet传参方便,这里仅做返回处理
    /// </summary>
    [HttpGet()]
    public bool Validate(string id, string code)
    {
        return CaptchaHelper.Validate(id, code);
    }
}

public class CaptchaResponse
{
    public string Id { get; set; }
    public string Base64 { get; set; }
}

具体示例请参照 Sample.MvcFramework项目。

常见问题

1. linux下如何运行

v2.0.9起开始内置SkiaSharp.NativeAssets.Linux.NoDependencies,无需安装其他依赖包。 低于v2.0.9除安装Lazy.Captcha.Core外,还需要安装SkiaSharp.NativeAssets.Linux.NoDependencies,更多细节请查看SkiaSharp官方文档。

如果运行时出现如下类似错误(相关issue):

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
---> System.TypeInitializationException: The type initializer for 'Lazy.Captcha.Core.DefaultFontFamilys' threw an exception.
---> System.TypeInitializationException: The type initializer for 'SkiaSharp.SKTypeface' threw an exception.
---> System.DllNotFoundException: Unable to load shared library 'libSkiaSharp' or one of its dependencies. 

可以尝试安装fontconfig

yum install fontconfig  // centos
apt-get install fontconfig // ubuntu

2. docker发布注意事项

需要安装fontconfig, 具体参考Sample.NetCore示例项目Dockerfile

3. 仅生成验证码图片,不需要LazyCaptcha存储验证,怎么做?(相关issue)

var imageGenerator = new DefaultCaptchaImageGenerator();
var imageGeneratorOption = new CaptchaImageGeneratorOption()
{
    // 必须设置
    ForegroundColors = DefaultColors.Colors
};
var bytes = imageGenerator.Generate("hello", imageGeneratorOption);

版本历史

v2.2.1

  • 修复StorageKeyPrefix拼写错误的问题,同时兼容错误的拼写StoreageKeyPrefix
  • 修复注释乱码的问题

详细内容参考:修正部分拼写

v2.2.0

  • 新增验证码生成频率限制功能。
  • 支持基于IP地址、用户ID或自定义键的频率限制策略。
  • 提供简单优雅的内存频率限制器实现。
  • 新增 GenerateWithRateLimitCheckRateLimit 方法。
  • 新增 RateLimitExceededException 异常处理。

v2.1.0

  • 升级依赖包。
  • 图片格式调整png。

v2.0.9

  • 升级依赖包。
  • 内置SkiaSharp.NativeAssets.Linux.NoDependencies。

v2.0.8

  • 升级依赖包。
  • CaptchaHelper.Validate增加removeIfFail参数。

v2.0.7

  • 升级依赖包。
  • 修复gif生成干扰线设置不正确的bug。

v2.0.6

  • 增强数学运算安全性。
  • 优化部分代码。

详细内容参考:11 数字运算方法完全替换以及两个并发问题

v2.0.5

  • 增加removeIfFail,交由开发者来决定验证码是否只能用一次。

v2.0.4

  • 裁剪kaiti字体文件,使dll大小从14M缩减为不到1M.

v2.0.3

  • 增加粗体配置项。加粗后文字更清晰。

TextBold= false 输入图片说明
TextBold = true 输入图片说明

  • 文字颜色随机时,保持各个文字颜色不同。
  • 优化部分代码。

v2.0.2

  • 去除启动时冗余调试信息

v2.0.1

  • 优化减法算术表达式,避免结果负数(目前仅限两个操作数)。
  • 优化验证码绘图显示。

v2.0.0

  • 绘图改为SkiaSharp.
MIT License Copyright (c) 2022 pojianbing Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

仿EasyCaptcha的.net core 下的图形验证码 展开 收起
README
MIT
取消

发行版 (3)

全部
2年前

近期动态

13小时前推送了新的提交到 master 分支,30dce01...9fb489c
13小时前合并了 PR #18 在库代码中不再使用 DefaultFontFamilies、DefaultColors 的 Instance,而是直接访问类中的列表、字体
1天前创建了 PR #18 在库代码中不再使用 DefaultFontFamilies、DefaultColors 的 Instance,而是直接访问类中的列表、字体
2天前推送了新的提交到 master 分支,f7b549d...30dce01
2天前推送了新的提交到 master 分支,1d61d02...f7b549d
加载更多
不能加载更多了
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
C#
1
https://gitee.com/pojianbing/lazy-captcha.git
git@gitee.com:pojianbing/lazy-captcha.git
pojianbing
lazy-captcha
LazyCaptcha
master

搜索帮助