ASP.NET MVC 验证码演示:从基础实现到最佳实践

验证码(Completely Automated Public Turing test to tell Computers and Humans Apart,全自动区分计算机和人类的公开图灵测试)是Web应用中抵御自动化攻击的基础手段。它通过要求用户识别扭曲的字符、图片或完成简单任务,有效防止机器人批量注册、登录、刷评论等行为。

在ASP.NET MVC中,验证码的实现方式主要分为两类:

  1. 自定义实现:通过GDI+生成图片验证码(灵活可控);
  2. 第三方库:如CaptchaMvc(快速集成,减少重复代码)。

本文将从基础概念手动实现进阶优化最佳实践,逐步讲解ASP.NET MVC中验证码的完整方案,并提供可直接运行的代码示例。

目录#

  1. 基础概念与环境准备
  2. 自定义验证码:从0到1实现
    • 2.1 验证码生成类(核心逻辑)
    • 2.2 控制器处理(返回图片流)
    • 2.3 视图整合(显示与刷新)
    • 2.4 验证逻辑(对比用户输入)
  3. 进阶优化:提升安全性与用户体验
    • 3.1 禁止缓存(避免验证码重复)
    • 3.2 增强验证码复杂度(干扰线、旋转、噪点)
    • 3.3 支持移动端适配
    • 3.4 分布式Session(集群环境兼容)
  4. 第三方库:CaptchaMvc快速集成
  5. 最佳实践汇总
  6. 常见问题排查
  7. 参考资料

1. 基础概念与环境准备#

1.1 核心概念#

  • 验证码文本:随机生成的字符串(字母+数字混合,长度4-6位最佳);
  • 图片渲染:通过GDI+将文本绘制成带干扰的图片(防止OCR识别);
  • 状态存储:将验证码文本存储在Session中(或分布式缓存),用于后续验证;
  • 验证逻辑:对比用户输入与存储的文本,一致则通过。

1.2 环境要求#

  • 开发工具:Visual Studio 2019+(或Rider);
  • .NET版本:.NET Framework 4.7.2+ 或 .NET Core 3.1+(本文以.NET Framework 4.8为例);
  • 依赖:GDI+(Windows系统自带,用于图形绘制)。

2. 自定义验证码:从0到1实现#

2.1 验证码生成类(核心逻辑)#

首先创建CaptchaGenerator类,负责生成随机文本和验证码图片。

代码实现#

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
 
public class CaptchaGenerator
{
    // 验证码字符集(排除易混淆字符:0/O、1/I)
    private const string CharSet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    private readonly Random _random = new Random();
 
    /// <summary>
    /// 生成随机验证码文本
    /// </summary>
    /// <param name="length">文本长度</param>
    /// <returns>随机字符串</returns>
    public string GenerateRandomText(int length = 4)
    {
        return new string(Enumerable.Repeat(CharSet, length)
            .Select(s => s[_random.Next(s.Length)]).ToArray());
    }
 
    /// <summary>
    /// 生成验证码图片
    /// </summary>
    /// <param name="captchaText">验证码文本</param>
    /// <param name="width">图片宽度</param>
    /// <param name="height">图片高度</param>
    /// <returns>验证码图片(Bitmap)</returns>
    public Bitmap GenerateCaptchaImage(string captchaText, int width = 200, int height = 50)
    {
        var bitmap = new Bitmap(width, height);
        
        using (var g = Graphics.FromImage(bitmap))
        {
            // 1. 初始化绘图设置(抗锯齿、高质量渲染)
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
 
            // 2. 绘制渐变背景(避免纯色易被切割)
            using (var gradientBrush = new LinearGradientBrush(
                new Rectangle(0, 0, width, height),
                Color.FromArgb(240, 245, 250), // 浅蓝
                Color.FromArgb(220, 230, 240), // 浅灰蓝
                15f))
            {
                g.FillRectangle(gradientBrush, 0, 0, width, height);
            }
 
            // 3. 绘制干扰线(15条随机颜色/位置)
            for (int i = 0; i < 15; i++)
            {
                var pen = new Pen(
                    Color.FromArgb(_random.Next(150, 200), _random.Next(150, 200), _random.Next(150, 200)),
                    1f);
                int x1 = _random.Next(width);
                int y1 = _random.Next(height);
                int x2 = _random.Next(width);
                int y2 = _random.Next(height);
                g.DrawLine(pen, x1, y1, x2, y2);
                pen.Dispose();
            }
 
            // 4. 绘制验证码文本(随机旋转、颜色)
            var font = new Font("Microsoft YaHei", 24f, FontStyle.Bold | FontStyle.Italic);
            var textBrush = new SolidBrush(
                Color.FromArgb(_random.Next(50, 100), _random.Next(50, 100), _random.Next(150, 200))); // 深蓝系
            var textSize = g.MeasureString(captchaText, font);
 
            // 随机旋转(±15度)
            float rotation = _random.Next(-15, 15);
            g.TranslateTransform(
                (width - textSize.Width) / 2, 
                (height - textSize.Height) / 2); // 居中
            g.RotateTransform(rotation);
            g.DrawString(captchaText, font, textBrush, 0, 0);
            g.ResetTransform(); // 重置旋转
 
            // 5. 绘制噪点(200个随机像素)
            for (int i = 0; i < 200; i++)
            {
                int x = _random.Next(width);
                int y = _random.Next(height);
                bitmap.SetPixel(x, y, Color.FromArgb(_random.Next(100, 200), _random.Next(100, 200), _random.Next(100, 200)));
            }
 
            // 6. 绘制边框(灰色细边)
            g.DrawRectangle(new Pen(Color.LightGray, 1f), 0, 0, width - 1, height - 1);
        }
 
        return bitmap;
    }
}

2.2 控制器处理(返回图片流)#

创建CaptchaController,实现GetCaptcha Action,负责:

  1. 生成验证码文本;
  2. 存储文本到Session;
  3. 生成图片并返回给客户端。

代码实现#

using System.Drawing.Imaging;
using System.IO;
using System.Web.Mvc;
 
public class CaptchaController : Controller
{
    public ActionResult GetCaptcha()
    {
        var generator = new CaptchaGenerator();
        string captchaText = generator.GenerateRandomText(4); // 4位验证码
 
        // 存储验证码到Session(用于后续验证)
        Session["CaptchaText"] = captchaText;
 
        // 关键:禁止浏览器缓存验证码(否则刷新页面验证码不变)
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1));
        Response.Cache.SetNoStore();
 
        // 生成图片并返回
        using (var bitmap = generator.GenerateCaptchaImage(captchaText))
        using (var ms = new MemoryStream())
        {
            bitmap.Save(ms, ImageFormat.Jpeg);
            return File(ms.ToArray(), "image/jpeg"); // 返回JPEG图片流
        }
    }
}

2.3 视图整合(显示与刷新)#

在登录/注册视图中,通过img标签加载验证码图片,并添加刷新按钮(用JavaScript避免缓存)。

视图代码(Login.cshtml)#

<div class="form-group">
    <label>验证码</label>
    <div class="captcha-box">
        <!-- 加载验证码图片 -->
        <img id="captchaImg" src="@Url.Action("GetCaptcha", "Captcha")" alt="验证码" class="captcha-img" />
        <!-- 刷新按钮 -->
        <button type="button" id="refreshBtn" class="btn btn-light">↻ 刷新</button>
    </div>
    <input type="text" id="Captcha" name="Captcha" class="form-control mt-2" placeholder="请输入验证码" required />
</div>
 
<!-- 刷新逻辑:添加时间戳参数避免缓存 -->
<script>
    document.getElementById("refreshBtn").addEventListener("click", function () {
        var captchaImg = document.getElementById("captchaImg");
        // 加时间戳参数,强制浏览器请求新图片
        captchaImg.src = "@Url.Action("GetCaptcha", "Captcha")?t=" + new Date().getTime();
    });
</script>

2.4 验证逻辑(对比用户输入)#

在登录/注册的Action中,获取用户输入的验证码,与Session中的值对比。

登录控制器代码(AccountController)#

public class AccountController : Controller
{
    [HttpPost]
    public ActionResult Login(string username, string password, string Captcha)
    {
        // 1. 验证验证码(优先验证,减少无效数据库查询)
        string storedCaptcha = Session["CaptchaText"] as string;
        if (string.IsNullOrEmpty(storedCaptcha) || 
            !string.Equals(Captcha, storedCaptcha, StringComparison.OrdinalIgnoreCase))
        {
            ModelState.AddModelError("Captcha", "验证码错误");
            return View();
        }
 
        // 2. 验证用户名密码(省略实际逻辑)
        if (username == "admin" && password == "123456")
        {
            return RedirectToAction("Index", "Home");
        }
 
        ModelState.AddModelError("", "用户名或密码错误");
        return View();
    }
}

3. 进阶优化:提升安全性与用户体验#

3.1 禁止缓存(关键优化)#

浏览器默认会缓存图片资源,导致刷新页面后验证码不变。需在GetCaptcha Action中添加以下响应头:

Response.Cache.SetCacheability(HttpCacheability.NoCache); // 不缓存
Response.Cache.SetExpires(DateTime.UtcNow.AddHours(-1)); // 过期时间设为过去
Response.Cache.SetNoStore(); // 禁止存储到缓存

3.2 增强验证码复杂度#

  • 字符旋转:通过RotateTransform随机旋转文本(±15度),增加OCR识别难度;
  • 渐变背景:避免纯色背景被轻易切割;
  • 干扰线/噪点:增加随机线条和像素点,干扰机器识别;
  • 字符集优化:排除易混淆字符(如0/O、1/I、L/1),提升用户识别率。

3.3 支持移动端适配#

  • 调整验证码图片尺寸(如width=150height=40),适配手机屏幕;
  • 增加图片和输入框的间距,避免误触;
  • 使用响应式CSS:
    .captcha-img {
        width: 100%;
        height: auto;
    }
    .captcha-box {
        display: flex;
        align-items: center;
        gap: 10px;
    }

3.4 分布式Session(集群环境兼容)#

如果应用部署在多服务器集群,默认的InProc Session(进程内存储)会导致不同服务器间Session不共享,验证码验证失败。需改用分布式Session

  1. 安装Redis:Install-Package Microsoft.Web.RedisSessionStateProvider
  2. 配置Web.config:
    <sessionState mode="Custom" customProvider="RedisSessionProvider">
      <providers>
        <add name="RedisSessionProvider" 
             type="Microsoft.Web.Redis.RedisSessionStateProvider" 
             host="localhost" 
             port="6379" 
             accessKey="" 
             ssl="false" />
      </providers>
    </sessionState>

4. 第三方库:CaptchaMvc快速集成#

如果不想手动实现验证码,可使用CaptchaMvc(专为ASP.NET MVC设计的开源库),简化开发流程。

4.1 安装与配置#

  1. 安装NuGet包:
    Install-Package CaptchaMvc.Mvc5
  2. Global.asax中注册:
    protected void Application_Start()
    {
        // 其他配置...
        CaptchaMvcConfig.RegisterRoutes(RouteTable.Routes);
    }

4.2 视图与控制器使用#

  • 视图:用@Html.Captcha()生成验证码:
    @Html.Captcha("Refresh", "Enter the code", 4) <!-- 4位验证码 -->
  • 控制器:用[CaptchaVerify]属性验证:
    [HttpPost]
    [CaptchaVerify("Captcha", "验证码错误")] // 验证名为"Captcha"的输入框
    public ActionResult Login(string username, string password)
    {
        // 验证通过,继续处理
    }

5. 最佳实践汇总#

  1. 优先验证验证码:在用户名密码验证前先验证验证码,减少无效数据库查询;
  2. Session安全
    • 避免存储敏感信息,仅存储验证码文本;
    • 集群环境用分布式Session(如Redis);
  3. 验证码复杂度
    • 长度≥4位,混合字母+数字;
    • 避免易混淆字符(0/O、1/I);
  4. 用户体验
    • 支持刷新按钮(无需刷新整个页面);
    • 忽略大小写验证(StringComparison.OrdinalIgnoreCase);
  5. 安全性
    • 禁止缓存验证码;
    • 限制验证码有效时间(如5分钟后过期):
      // 在GetCaptcha中存储生成时间
      Session["CaptchaExpireTime"] = DateTime.Now.AddMinutes(5);
      // 验证时检查过期时间
      if (DateTime.Now > (DateTime)Session["CaptchaExpireTime"])
      {
          ModelState.AddModelError("Captcha", "验证码已过期");
      }

6. 常见问题排查#

  1. 验证码不显示
    • 检查GetCaptcha Action是否返回图片流(File方法);
    • 检查img标签的src路径是否正确(如@Url.Action("GetCaptcha", "Captcha"));
  2. 刷新后验证码不变
    • 未添加时间戳参数(?t=时间戳);
    • 未禁止浏览器缓存(缺少Response.Cache设置);
  3. 验证码验证失败
    • Session过期或未存储(检查Session["CaptchaText"]是否有值);
    • 大小写敏感(未用StringComparison.OrdinalIgnoreCase);
  4. GDI+报错
    • 字体不存在(服务器上没有Microsoft YaHei,换为SimSun(宋体));
    • 图片资源未释放(确保用using包裹BitmapGraphics)。

7. 参考资料#

  1. MSDN 文档:GDI+ 图形编程
  2. ASP.NET MVC 官方文档:Session 状态管理
  3. CaptchaMvc 官方文档:GitHub 仓库
  4. OWASP 验证码最佳实践:CAPTCHA 控件

通过本文的实现,你可以快速为ASP.NET MVC应用添加安全、易用的验证码功能。自定义实现适合需要高度定制的场景,第三方库适合快速集成。根据项目需求选择合适的方案,同时遵循最佳实践,确保验证码的安全性和用户体验。