引言

在现代 Web 应用中,用户身份验证是安全体系的第一道防线。然而,单纯依赖用户名和密码的登录机制早已不堪一击——自动化脚本、暴力破解、爬虫攻击层出不穷。一个看似简单的“验证码”功能,实则承载着抵御机器流量的重要职责。

最近重构一个传统 Ja va Web 项目时,重新审视了这一经典安全组件的设计与实现。今天就来分享如何使用 JSP + Servlet + Ja vaScript 技术栈,从零搭建一个结构清晰、交互流畅、具备基础防攻击能力的验证码登录系统。

整个过程并不复杂,但涉及前后端协作、会话管理、图像生成等多个关键点。我们将一步步完成:动态验证码图片生成 → 前端展示与刷新 → 表单提交 → 后台校验全流程,并在最后探讨其可扩展性与未来演进方向。

动态验证码是如何工作的?

验证码的核心逻辑其实很朴素:服务器生成一段随机字符,绘制成干扰性强的图片供人识别,同时将原始值存储在当前用户的 Session 中;当用户输入后,后台比对输入值与 Session 中的记录是否一致。

这个过程中有几个关键设计必须注意:

接下来我们就通过三个核心模块来落地这套机制。

验证码图像生成(Servlet)

我们创建一个 VerifyCodeServlet,专门负责生成并输出 JPEG 图像。它被绑定到 /captcha 路径,前端只需 即可获取最新验证码。

package com.example.captcha;

import ja vax.imageio.ImageIO;
import ja vax.servlet.ServletException;
import ja vax.servlet.annotation.WebServlet;
import ja vax.servlet.http.HttpServlet;
import ja vax.servlet.http.HttpServletRequest;
import ja vax.servlet.http.HttpServletResponse;
import ja vax.servlet.http.HttpSession;
import ja va.awt.*;
import ja va.awt.image.BufferedImage;
import ja va.io.IOException;
import ja va.util.Random;

@WebServlet("/captcha")
public class VerifyCodeServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private int width = 100;
    private int height = 40;
    private int codeCount = 4;
    private int lineCount = 50;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("image/jpeg");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();

        g.setColor(Color.WHITE);
        g.fillRect(0, 0, width, height);
        g.setColor(Color.GRAY);
        g.drawRect(0, 0, width - 1, height - 1);

        Font font = new Font("Arial", Font.BOLD | Font.ITALIC, 28);
        g.setFont(font);

        char[] chars = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
                         'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U',
                         'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5',
                         '6', '7', '8', '9' };

        Random random = new Random();
        StringBuilder codeBuilder = new StringBuilder();

        // 绘制干扰线
        g.setColor(Color.LIGHT_GRAY);
        for (int i = 0; i < lineCount; i++) {
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            g.drawLine(x1, y1, x2, y2);
        }

        // 绘制字符
        for (int i = 0; i < codeCount; i++) {
            String c = String.valueOf(chars[random.nextInt(chars.length)]);
            codeBuilder.append(c);
            g.setColor(new Color(random.nextInt(100), random.nextInt(100), random.nextInt(100)));
            g.drawString(c, 20 + i * 18, 28);
        }

        HttpSession session = request.getSession();
        session.setAttribute("verify_code", codeBuilder.toString().toLowerCase());

        ImageIO.write(image, "jpeg", response.getOutputStream());
        g.dispose();
    }
}

这段代码有几个值得注意的细节:

小技巧:如果你希望支持中文提示词渲染,可以考虑引入 TTF 字体文件并调用 g.setFont() 加载自定义字体,这对国际化项目尤其有用。

登录页面设计(Login.jsp)

前端页面采用纯 JSP 编写,结合内联 CSS 和轻量 JS 实现良好视觉效果与交互体验。

<%@ page language="ja va" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
    String basePath = request.getScheme() + "://" +
                       request.getServerName() + ":" +
                       request.getServerPort() +
                       request.getContextPath() + "/";
%>



    
    
    用户登录
    






这里的关键在于验证码图片的动态加载机制:

这种做法简单高效,无需额外 AJAX 请求或 DOM 操作,非常适合轻量级场景。

登录逻辑处理(UserLoginServlet.ja va)

最后是登录表单的接收与验证逻辑:

package com.example.captcha;

import ja vax.servlet.ServletException;
import ja vax.servlet.annotation.WebServlet;
import ja vax.servlet.http.HttpServlet;
import ja vax.servlet.http.HttpServletRequest;
import ja vax.servlet.http.HttpServletResponse;
import ja vax.servlet.http.HttpSession;
import ja va.io.IOException;
import ja va.io.PrintWriter;

@WebServlet("/userLogin")
public class UserLoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        PrintWriter out = response.getWriter();
        HttpSession session = request.getSession();

        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String userInput = request.getParameter("verifyCode").toLowerCase();
        String realCode = (String) session.getAttribute("verify_code");

        if (realCode == null || !realCode.equals(userInput)) {
            out.println("");
            return;
        }

        if ("admin".equals(username) && "123456".equals(password)) {
            out.println("

✅ 登录成功!欢迎回来," + username + "

"); session.removeAttribute("verify_code"); // 防止重复使用 } else { out.println(""); } } }

几点说明:

安全提醒:生产环境中应使用加密存储密码(如 BCrypt),并通过 PreparedStatements 防止 SQL 注入。

可行的优化方向

虽然上述方案已能满足基本需求,但在真实项目中还可以进一步增强:

优化项 实现方式
添加噪点 在图像上绘制随机像素点,增加 OCR 识别难度
设置过期时间 web.xml 中设置 Session 超时(如 1
AJAX 提交 改用异步提交,避免整页刷新,提升 UX
返回 JSON 后端返回结构化数据,便于前后端分离架构集成
滑动/点选验证码 引入更复杂的交互式验证机制,适合高安全要求场景

例如,你可以将当前的同步表单提交改为 Fetch API 异步请求:

document.querySelector('form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const res = await fetch('userLogin', {
        method: 'POST',
        body: formData
    });
    const html = await res.text();
    document.body.innerHTML = html;
});

配合后端返回 JSON 响应,即可实现无刷新登录体验。

写在最后:传统验证码的未来在哪里?

我们今天实现的是典型的“图像+文本”型验证码,属于 CAPTCHA 的初级形态。随着 AI 视觉技术的发展,这类简单干扰图像正变得越来越容易被破解。

但反过来看,AI 也在催生新一代智能验证方式。比如阿里 recently 开源的 Z-Image-ComfyUI 模型,具备强大的文生图能力和语义理解水平,完全可以用来构建“智能图形挑战”:

这些基于语义理解的任务,机器难以批量伪造,而人类却能轻松应对。未来的验证码可能不再是“你是不是人”,而是“你有没有人类的理解力”。

当然,在绝大多数普通业务系统中,传统的字符验证码依然够用且成本低廉。只要合理设计、及时更新策略,它仍然是性价比极高的安全防护手段。

如果你正在做一个学生管理系统、内部办公平台或小型 CMS,本文这套 JSP + Servlet 方案完全足够快速上线;

如果你关注前沿趋势,不妨尝试将 Z-Image 这类 AIGC 工具接入验证流程,探索下一代人机识别的可能性。

技术没有高低之分,只有适不适合。守住底线安全,同时保持对未来的敏感,这才是开发者应有的姿态。

本文转载于:https://www.jb51.net/javascript/356315sjt.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。