无私分享:ASP.NET CORE 项目实战(第十四章)图形验证码的实现

在现代 Web 应用中,图形验证码是防止自动化攻击(如暴力破解、批量注册等)的有效手段。本文将通过 ASP.NET Core 实现一个高度安全的图形验证码系统,包含随机字符生成、干扰元素绘制、缓存存储及校验逻辑。我们将使用纯代码生成验证码图片,避免第三方依赖,保证系统的安全性和灵活性。

目录#

  1. 图形验证码基础
    • 为什么需要图形验证码?
    • 常见实现方式对比
  2. ASP.NET Core 实现方案
    • 项目结构概述
    • 核心类设计
  3. 验证码生成技术细节
    • 随机字符串生成算法
    • 使用 System.Drawing 绘制图片
    • 添加干扰元素
  4. 验证码的存储与校验
    • 使用内存缓存存储验证码
    • 校验逻辑实现
  5. 集成到 MVC 系统
    • 控制器实现
    • Razor视图集成
  6. 最佳实践与安全性
    • 防破解设计
    • 性能优化建议
  7. 示例用法
    • 登录页面集成
    • AJAX验证实现
  8. 完整代码参考
  9. 参考资源

1. 图形验证码基础#

为什么需要图形验证码?#

图形验证码主要用于:

  • 防止自动化脚本攻击
  • 降低暴力破解密码的成功率
  • 防止垃圾注册和垃圾评论
  • 区分人类用户和机器人程序

常见实现方式对比#

实现方式优点缺点
第三方API实现简单,功能丰富依赖外部服务,可能存在费用
纯代码生成完全可控,无额外依赖实现复杂度较高
JavaScript 库前端生成,减轻服务器压力安全性较低,易被绕过

推荐选择:ASP.NET Core 服务端生成,保证安全性与可控性。


2. ASP.NET Core 实现方案#

项目结构#

/Controllers
    /AccountController.cs
/Services
    /CaptchaService.cs
/Views
    /Account
        /Login.cshtml

核心类设计#

public interface ICaptchaService
{
    byte[] GenerateCaptcha(out string captchaText);
    bool ValidateCaptcha(string userInput);
}

3. 验证码生成技术细节#

步骤 1:安装必要 NuGet 包#

Install-Package System.Drawing.Common
Install-Package Microsoft.Extensions.Caching.Memory

步骤 2:生成随机字符串#

private string GenerateRandomText(int length = 4)
{
    // 排除易混淆字符:0O1Il
    const string validChars = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
    var random = new Random();
    return new string(Enumerable.Repeat(validChars, length)
        .Select(s => s[random.Next(s.Length)]).ToArray());
}

步骤 3:使用 System.Drawing 绘制图片#

public byte[] GenerateCaptcha(out string captchaText)
{
    captchaText = GenerateRandomText();
    
    using var bitmap = new Bitmap(120, 40);
    using var graphics = Graphics.FromImage(bitmap);
    
    // 设置背景色
    graphics.Clear(Color.White);
    
    // 绘制文字
    using var font = new Font("Arial", 18, FontStyle.Bold);
    using var brush = new SolidBrush(Color.Black);
    graphics.DrawString(captchaText, font, brush, 10, 10);
 
    // 添加干扰线
    var random = new Random();
    for (int i = 0; i < 5; i++)
    {
        using var pen = new Pen(GetRandomColor(random));
        graphics.DrawLine(pen,
            random.Next(0, 120), random.Next(0, 40),
            random.Next(0, 120), random.Next(0, 40));
    }
 
    // 添加噪点
    for (int i = 0; i < 500; i++)
    {
        bitmap.SetPixel(random.Next(0, 120), random.Next(0, 40), GetRandomColor(random));
    }
    
    // 保存到内存流
    using var stream = new MemoryStream();
    bitmap.Save(stream, ImageFormat.Png);
    return stream.ToArray();
}

步骤 4:高级干扰技术#

// 波纹扭曲效果(增强防OCR能力)
private void ApplyWaveDistortion(Bitmap bitmap)
{
    var clone = (Bitmap)bitmap.Clone();
    var rnd = new Random();
    for (int y = 0; y < clone.Height; y++)
    {
        for (int x = 0; x < clone.Width; x++)
        {
            var newX = (int)(x + (Math.Sin(y * 0.05) * 2));
            var newY = (int)(y + (Math.Cos(x * 0.05) * 2));
            newX = Math.Clamp(newX, 0, clone.Width - 1);
            newY = Math.Clamp(newY, 0, clone.Height - 1);
            bitmap.SetPixel(x, y, clone.GetPixel(newX, newY));
        }
    }
}

4. 验证码的存储与校验#

使用内存缓存存储#

public class CaptchaService : ICaptchaService
{
    private readonly IMemoryCache _cache;
    private const string CachePrefix = "Captcha_";
    
    public CaptchaService(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
    }
 
    public byte[] GenerateCaptcha(out string captchaText)
    {
        captchaText = GenerateRandomText();
        var cacheKey = $"{CachePrefix}{Guid.NewGuid()}";
        
        // 缓存5分钟
        _cache.Set(cacheKey, captchaText, 
            new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(5)));
        
        return GenerateImage(captchaText);
    }
 
    public bool ValidateCaptcha(string cacheKey, string userInput)
    {
        if (string.IsNullOrEmpty(cacheKey) || 
            string.IsNullOrEmpty(userInput)) 
            return false;
 
        if (_cache.TryGetValue($"{CachePrefix}{cacheKey}", out string validText))
        {
            // 立即清除已使用的验证码
            _cache.Remove(cacheKey);
            return string.Equals(
                userInput, validText, 
                StringComparison.OrdinalIgnoreCase); // 忽略大小写
        }
        return false;
    }
}

5. 集成到 MVC 系统#

控制器实现#

public class AccountController : Controller
{
    private readonly ICaptchaService _captchaService;
    
    public AccountController(ICaptchaService captchaService)
    {
        _captchaService = captchaService;
    }
    
    [HttpGet("captcha")]
    public IActionResult GetCaptcha()
    {
        var imageBytes = _captchaService.GenerateCaptcha(out string captchaText);
        
        // 将验证码标识符存储在TempData中(或通过其他方式传递到前端)
        HttpContext.Response.Cookies.Append(
            "captcha_key", 
            Guid.NewGuid().ToString(),
            new CookieOptions { HttpOnly = true });
            
        return File(imageBytes, "image/png");
    }
    
    [HttpPost("login")]
    public IActionResult Login(LoginModel model)
    {
        // 获取前端传递的标识符
        var captchaKey = Request.Cookies["captcha_key"];
        if (!_captchaService.ValidateCaptcha(captchaKey, model.Captcha))
        {
            ModelState.AddModelError("Captcha", "验证码错误");
            return View(model);
        }
        
        // ... 其他登录逻辑
    }
}

Razor视图集成#

<!-- Login.cshtml -->
<form asp-action="Login">
  <div class="form-group">
    <label>验证码</label>
    <div class="input-group">
      <input type="text" class="form-control" name="Captcha" />
      <div class="input-group-append">
        <img id="captchaImg" src="/captcha" 
             alt="点击刷新验证码" 
             style="cursor:pointer;height:40px;" />
      </div>
    </div>
    <input type="hidden" id="captchaKey" name="CaptchaKey" />
  </div>
  
  <button type="submit">登录</button>
</form>
 
@section Scripts{
<script>
  // 点击刷新验证码
  $("#captchaImg").click(function() {
      $(this).attr("src", "/captcha?t=" + new Date().getTime());
  });
  
  // 初始化时获取验证码标识符
  document.cookie.split(';').forEach(cookie => {
      const [name, value] = cookie.trim().split('=');
      if(name === "captcha_key") {
          $("#captchaKey").val(value);
      }
  });
</script>
}

6. 最佳实践与安全性#

防破解设计#

  1. 动态干扰元素:每次生成的干扰线、噪点位置随机
  2. 字符变形:添加旋转和扭曲效果
  3. 字符集优化:排除0/O、1/I等易混淆字符
  4. 长度控制:推荐4-6个字符,平衡用户体验与安全性
  5. 大小写处理:统一转换为大写或小写进行验证

性能优化#

// 单例模式重用对象池
private readonly ObjectPool<Graphics> _graphicsPool = 
    ObjectPool.Create<Graphics>();
 
// 在生成图片方法中使用对象池
using (var graphics = _graphicsPool.Get())
{
    // 使用graphics对象操作
    _graphicsPool.Return(graphics);
}

缓存策略建议#

策略适用场景
内存缓存 (IMemoryCache)单服务器部署
IDistributedCache多服务器/集群部署
Session存储对安全性要求极高的场景

7. 示例用法#

登录页面集成效果#

验证码演示

AJAX 验证实现#

// 前端AJAX验证
$("#captchaInput").blur(function() {
    const userInput = $(this).val();
    const key = $("#captchaKey").val();
    
    $.post("/api/validatecaptcha", { key, userInput })
     .done(function(valid) {
         if(!valid) {
             $("#captchaError").text("验证码不正确");
         }
     });
});
// 后端API接口
[ApiController]
[Route("api/validatecaptcha")]
public class CaptchaApiController : ControllerBase
{
    [HttpPost]
    public IActionResult Validate([FromBody] CaptchaRequest request)
    {
        var isValid = _captchaService.ValidateCaptcha(request.Key, request.Input);
        return Ok(isValid);
    }
}

8. 完整代码参考#

GitHub仓库地址
(包含完整可运行项目示例)

核心服务类完整代码:

public class CaptchaService : ICaptchaService
{
    private readonly IMemoryCache _cache;
    private const string CachePrefix = "Captcha_";
    private readonly ObjectPool<Random> _randomPool;
 
    public CaptchaService(IMemoryCache memoryCache)
    {
        _cache = memoryCache;
        _randomPool = ObjectPool.Create<Random>();
    }
 
    public byte[] GenerateCaptcha(out string captchaText, out string cacheKey)
    {
        using var leasedRandom = _randomPool.Get();
        var random = leasedRandom.Item;
        captchaText = GenerateRandomText(random);
        cacheKey = Guid.NewGuid().ToString("N");
        
        _cache.Set($"{CachePrefix}{cacheKey}", captchaText, 
            new MemoryCacheEntryOptions()
                .SetAbsoluteExpiration(TimeSpan.FromMinutes(5)));
        
        return GenerateImage(captchaText, random);
    }
 
    private byte[] GenerateImage(string text, Random random)
    {
        using var bitmap = new Bitmap(120, 40);
        using (var graphics = Graphics.FromImage(bitmap))
        {
            graphics.Clear(Color.White);
            DrawText(graphics, text, random);
            DrawNoise(bitmap, random);
            DrawLines(graphics, random);
        }
        ApplyDistortion(bitmap, random);
        
        using var stream = new MemoryStream();
        bitmap.Save(stream, ImageFormat.Png);
        return stream.ToArray();
    }
    
    // 其他辅助方法...
}

参考#

  1. Microsoft Docs: 缓存内存中
  2. System.Drawing.Common 官方文档
  3. OWASP 验证码指南
  4. ASP.NET Core 性能最佳实践

希望这篇详细教程能帮助您在ASP.NET Core项目中成功实现图形验证码功能。如在实际使用中遇到问题或有优化建议,欢迎在评论区交流!