杰瑞科技汇

Java Servlet验证码如何实现?

我们将分步实现一个简单的图形验证码,并将其集成到登录页面中。

Java Servlet验证码如何实现?-图1
(图片来源网络,侵删)

核心思路

  1. 生成验证码图片

    • 在服务器端,创建一个 BufferedImage 对象,这相当于一张内存中的画布。
    • 获取该画布的 Graphics2D 对象,这是我们的“画笔”。
    • 使用“画笔”在画布上绘制背景色、干扰线、随机字符(验证码)等。
    • 将绘制好的图片,通过 response.getOutputStream() 写回到客户端的浏览器。
  2. 存储验证码

    • 为了后续验证,服务器必须知道它刚刚生成了什么验证码。
    • 最简单的存储方式是使用 HttpSession,将生成的验证码字符串存入 session 中。
  3. 前端请求与显示

    • 在登录页面的 <img> 标签中,将 src 属性指向 Servlet 的 URL(/captchaServlet)。
    • 当页面加载时,浏览器会自动请求这个 Servlet,从而获取并显示验证码图片。
  4. 用户提交与验证

    Java Servlet验证码如何实现?-图2
    (图片来源网络,侵删)
    • 用户在登录表单中输入用户名、密码和验证码。
    • 表单提交到另一个 Servlet(LoginServlet)。
    • LoginServletrequest 中获取用户输入的验证码,并与 session 中存储的验证码进行比较。
    • 比较成功则验证通过,失败则提示错误。

创建生成验证码的 Servlet (CaptchaServlet.java)

这个 Servlet 的唯一职责就是生成图片并发送。

import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
@WebServlet("/captchaServlet")
public class CaptchaServlet extends HttpServlet {
    // 验证码字符集
    private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789";
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 设置响应内容类型为图片
        response.setContentType("image/png");
        // 2. 创建一个 BufferedImage 对象作为画布
        int width = 120;
        int height = 40;
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        // 3. 获取 Graphics2D 对象,作为画笔
        Graphics2D g2d = image.createGraphics();
        // 4. 绘制背景
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, width, height);
        // 5. 绘制干扰线
        Random random = new Random();
        g2d.setColor(Color.LIGHT_GRAY);
        for (int i = 0; i < 8; i++) {
            int x1 = random.nextInt(width);
            int y1 = random.nextInt(height);
            int x2 = random.nextInt(width);
            int y2 = random.nextInt(height);
            g2d.drawLine(x1, y1, x2, y2);
        }
        // 6. 生成随机验证码
        StringBuilder captcha = new StringBuilder();
        g2d.setFont(new Font("Arial", Font.BOLD, 24));
        for (int i = 0; i < 4; i++) {
            char c = CHARACTERS.charAt(random.nextInt(CHARACTERS.length()));
            captcha.append(c);
            // 设置随机颜色
            g2d.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            // 绘制字符,位置稍微错开
            g2d.drawString(String.valueOf(c), 25 * i + 10, 28);
        }
        // 7. 将验证码存入 session
        HttpSession session = request.getSession();
        session.setAttribute("captcha", captcha.toString());
        // 8. 释放资源
        g2d.dispose();
        // 9. 将图片输出到 response 的输出流中
        ImageIO.write(image, "png", response.getOutputStream());
    }
}

代码解释

  • @WebServlet("/captchaServlet"): 将 Servlet 映射到 /captchaServlet 这个 URL。
  • response.setContentType("image/png"): 告诉浏览器我们返回的是一个 PNG 图片。
  • BufferedImage: 内存中的图像对象。
  • Graphics2D: Java 2D 绘图 API,用于在图像上绘制各种形状和文本。
  • session.setAttribute("captcha", captcha.toString()): 这是关键一步,将生成的验证码字符串存入 session,以便后续验证。
  • ImageIO.write(...): 将 BufferedImage 对象写入到 HTTP 响应的输出流,浏览器就能显示它了。

创建登录页面 (login.html)

这个页面包含一个表单和一个用于显示验证码的 <img>

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">登录</title>
    <style>
        body { font-family: sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f2f5; }
        .login-box { border: 1px solid #ccc; padding: 20px; border-radius: 8px; background-color: white; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], input[type="password"] { width: 100%; padding: 8px; box-sizing: border-box; border: 1px solid #ccc; border-radius: 4px; }
        .captcha-container { display: flex; align-items: center; }
        #captchaImage { margin-left: 10px; cursor: pointer; border: 1px solid #ccc; }
        button { width: 100%; padding: 10px; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
        button:hover { background-color: #0056b3; }
        #errorMsg { color: red; text-align: center; margin-bottom: 10px; }
    </style>
</head>
<body>
    <div class="login-box">
        <h2>用户登录</h2>
        <div id="errorMsg" style="display: none;"></div>
        <form id="loginForm" action="loginServlet" method="post">
            <div class="form-group">
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="form-group">
                <label for="captcha">验证码:</label>
                <div class="captcha-container">
                    <input type="text" id="captcha" name="captcha" required>
                    <!-- 点击图片可以刷新验证码 -->
                    <img id="captchaImage" src="captchaServlet" alt="验证码" title="点击刷新">
                </div>
            </div>
            <button type="submit">登录</button>
        </form>
    </div>
    <script>
        // 点击验证码图片时刷新
        document.getElementById('captchaImage').addEventListener('click', function() {
            // 添加一个时间戳,防止浏览器缓存
            this.src = 'captchaServlet?' + new Date().getTime();
        });
        // 使用 AJAX 提交表单,避免页面刷新
        document.getElementById('loginForm').addEventListener('submit', function(event) {
            event.preventDefault(); // 阻止表单默认提交行为
            const formData = new FormData(this);
            const errorMsg = document.getElementById('errorMsg');
            fetch('loginServlet', {
                method: 'POST',
                body: formData
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    alert('登录成功!');
                    // 登录成功后可以跳转页面
                    // window.location.href = 'welcome.html';
                } else {
                    errorMsg.textContent = data.message;
                    errorMsg.style.display = 'block';
                    // 刷新验证码
                    document.getElementById('captchaImage').src = 'captchaServlet?' + new Date().getTime();
                }
            })
            .catch(error => {
                console.error('Error:', error);
                errorMsg.textContent = '登录请求失败,请稍后重试。';
                errorMsg.style.display = 'block';
            });
        });
    </script>
</body>
</html>

关键点

  • <img src="captchaServlet">: 这行代码会触发 CaptchaServletdoGet 方法。
  • 点击 <img> 标签会刷新图片:this.src = 'captchaServlet?' + new Date().getTime();,添加时间戳是为了防止浏览器使用缓存的旧图片。
  • 使用 fetch (AJAX) 提交表单,这样验证失败时,页面不会刷新,用户体验更好。

创建处理登录的 Servlet (LoginServlet.java)

这个 Servlet 接收登录请求,并验证用户输入的验证码。

Java Servlet验证码如何实现?-图3
(图片来源网络,侵删)
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import org.json.JSONObject; // 需要引入 org.json 库
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        request.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=UTF-8");
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        String inputCaptcha = request.getParameter("captcha");
        HttpSession session = request.getSession();
        String sessionCaptcha = (String) session.getAttribute("captcha");
        JSONObject jsonResponse = new JSONObject();
        // 验证逻辑
        if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(inputCaptcha)) {
            // 验证码错误
            jsonResponse.put("success", false);
            jsonResponse.put("message", "验证码错误!");
        } else {
            // 验证码正确,这里可以继续验证用户名和密码
            // if ("admin".equals(username) && "password".equals(password)) {
            //     jsonResponse.put("success", true);
            //     jsonResponse.put("message", "登录成功!");
            // } else {
            //     jsonResponse.put("success", false);
            //     jsonResponse.put("message", "用户名或密码错误!");
            // }
            // 为了简化,我们只验证验证码
            jsonResponse.put("success", true);
            jsonResponse.put("message", "登录成功!");
        }
        // 验证完成后,无论成功与否,都应从 session 中移除验证码,防止重复使用
        session.removeAttribute("captcha");
        // 将 JSON 响应写回客户端
        response.getWriter().write(jsonResponse.toString());
    }
}

代码解释

  • response.setContentType("application/json;charset=UTF-8"): 告诉浏览器我们返回的是 JSON 数据。
  • request.getParameter("captcha"): 获取前端表单提交的验证码。
  • session.getAttribute("captcha"): 从 session 中获取之前存储的验证码。
  • equalsIgnoreCase(): 使用不区分大小写的方式比较验证码,这是最佳实践。
  • session.removeAttribute("captcha"): 非常重要! 验证成功或失败后,立即从 session 中删除验证码,这可以防止“重放攻击”,即用户用同一个验证码多次提交。
  • response.getWriter().write(jsonResponse.toString()): 将构建的 JSON 对象发送回前端。

项目结构与依赖

项目结构

your-web-app/
├── src/
│   └── main/
│       ├── java/
│       │   └── yourpackage/
│       │       ├── CaptchaServlet.java
│       │       └── LoginServlet.java
│       └── webapp/
│           ├── login.html
│           └── WEB-INF/
│               └── web.xml (可选,如果使用注解则不需要)
└── pom.xml (如果你使用 Maven)

Maven 依赖 (pom.xml)

如果你使用 Maven 构建项目,需要添加 org.json 库的依赖。

<dependencies>
    <!-- Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!-- JSON 库 -->
    <dependency>
        <groupId>org.json</groupId>
        <artifactId>json</artifactId>
        <version>20251013</version>
    </dependency>
</dependencies>

总结与最佳实践

  1. 验证码是临时性的:验证码一旦被验证(无论成功与否),就应该立即从 session 中删除,防止被重复使用。
  2. 不区分大小写:用户输入验证码时,大小写容易混淆,服务器端验证时应忽略大小写。
  3. 增加干扰元素:为了防止 OCR(光学字符识别)攻击,可以增加更复杂的干扰线、噪点、扭曲等。
  4. 考虑使用验证码库:对于生产环境,可以考虑使用成熟的验证码库,如 Kaptcha 或 Google 的 reCAPTCHA,它们提供了更强大、更安全的验证功能。
  5. 安全性:验证码主要用于防止自动化脚本(机器人)攻击,而不是防止暴力破解密码,密码的安全应由后端逻辑(如加盐哈希存储)来保证。
分享:
扫描分享到社交APP
上一篇
下一篇