杰瑞科技汇

Java Servlet验证码如何实现与校验?

我们将使用 JDK 内置的 java.awtjava.imageio 库来生成图片,这样就不需要引入第三方依赖,非常适合学习和理解原理。

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

实现步骤概览

  1. 创建 Servlet 类: 编写一个 Servlet,用于生成验证码图片并发送到客户端。
  2. 生成随机验证码字符串: 生成一个随机字符串(如数字、字母组合)。
  3. 将验证码存入 Session: 将生成的字符串存入 HttpSession 中,以便后续在登录等操作中进行验证。
  4. 绘制验证码图片:
    • 创建一个内存中的 BufferedImage 画布。
    • 设置背景色。
    • 绘制干扰线。
    • 绘制随机字符。
    • 添加干扰点。
    • 可能的话,进行扭曲处理,增加识别难度。
  5. 将图片输出到客户端: 使用 ImageIOBufferedImage 对象以 PNG 或 JPEG 格式输出到 HttpServletResponse 的输出流中。
  6. 在 JSP 页面中调用: 创建一个 JSP 页面,使用 <img> 标签的 src 属性来请求这个 Servlet,从而显示验证码。
  7. 编写验证逻辑: 在处理用户登录的 Servlet 中,从 Session 中取出验证码,并与用户提交的验证码进行比较。

第 1 步:创建生成验证码的 Servlet (ImageCodeServlet.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("/imageCode") // 使用注解映射 Servlet 访问路径
public class ImageCodeServlet extends HttpServlet {
    // 验证码字符集 (可选的字符)
    private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
    private static final int CODE_LENGTH = 4; // 验证码长度
    private static final int WIDTH = 120;    // 图片宽度
    private static final int HEIGHT = 40;    // 图片高度
    private static final int FONT_SIZE = 25; // 字体大小
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 生成随机验证码
        String code = generateCode(CODE_LENGTH);
        // 2. 将验证码存入 Session
        HttpSession session = req.getSession();
        session.setAttribute("imageCode", code); // key 为 "imageCode",value 为生成的字符串
        // 3. 创建图片画布
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        // 4. 绘制背景
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        // 5. 绘制干扰线
        drawRandomLines(g2d);
        // 6. 绘制验证码字符
        drawRandomCharacters(g2d, code);
        // 7. 释放资源
        g2d.dispose();
        // 8. 设置响应头,禁止浏览器缓存图片
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        // 9. 将图片以 "image/png" 格式输出到客户端
        resp.setContentType("image/png");
        ImageIO.write(image, "png", resp.getOutputStream());
    }
    /**
     * 生成指定长度的随机字符串
     */
    private String generateCode(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(CHARACTERS.charAt(random.nextInt(CHARACTERS.length())));
        }
        return sb.toString();
    }
    /**
     * 绘制干扰线
     */
    private void drawRandomLines(Graphics2D g2d) {
        Random random = new Random();
        g2d.setColor(getRandomColor(160, 200));
        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);
        }
    }
    /**
     * 绘制验证码字符
     */
    private void drawRandomCharacters(Graphics2D g2d, String code) {
        Random random = new Random();
        int x = 15;
        int y = HEIGHT / 2 + FONT_SIZE / 2;
        for (int i = 0; i < code.length(); i++) {
            // 随机字体样式
            Font font = new Font("Arial", Font.BOLD | Font.ITALIC, FONT_SIZE);
            g2d.setFont(font);
            // 随机颜色
            g2d.setColor(getRandomColor(20, 130));
            // 随机旋转角度 (-30 到 30 度)
            int angle = random.nextInt(60) - 30;
            double theta = angle * Math.PI / 180;
            // 保存当前状态
            g2d.save();
            // 平移和旋转
            g2d.translate(x, y);
            g2d.rotate(theta);
            // 绘制字符
            g2d.drawString(String.valueOf(code.charAt(i)), 0, 0);
            // 恢复状态
            g2d.restore();
            x += WIDTH / CODE_LENGTH;
        }
    }
    /**
     * 生成随机颜色
     */
    private Color getRandomColor(int min, int max) {
        Random random = new Random();
        int r = min + random.nextInt(max - min);
        int g = min + random.nextInt(max - min);
        int b = min + random.nextInt(max - min);
        return new Color(r, g, b);
    }
}

第 2 步:创建 JSP 页面 (login.jsp)

这个页面包含一个表单,用于用户输入用户名、密码和验证码,关键在于 <img>

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>登录页面</title>
    <style>
        .code-container {
            display: flex;
            align-items: center;
        }
        #imageCode {
            margin-left: 10px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h2>用户登录</h2>
    <form action="login" method="post">
        用户名: <input type="text" name="username"><br><br>
        密码: <input type="password" name="password"><br><br>
        验证码: 
        <div class="code-container">
            <input type="text" name="userCode" maxlength="4" style="width: 80px;">
            <img id="imageCode" src="imageCode" title="点击刷新" alt="验证码图片">
        </div>
        <br><br>
        <input type="submit" value="登录">
    </form>
    <script>
        // 点击图片刷新验证码
        document.getElementById("imageCode").onclick = function() {
            // 加上时间戳,防止浏览器缓存图片导致不刷新
            this.src = "imageCode?" + new Date().getTime();
        };
    </script>
</body>
</html>

JSP 关键点说明:

  • <img src="imageCode">: 这里的 src="imageCode" 正好对应了我们 ImageCodeServlet 上的 @WebServlet("/imageCode") 注解,浏览器会向这个 URL 发起 GET 请求,Servlet 就会返回图片流。
  • <script> 部分: 为了提升用户体验,我们添加了点击图片刷新验证码的功能。? + new Date().getTime() 是一个常用技巧,可以强制浏览器不使用缓存的图片,确保每次请求都是新的验证码。

第 3 步:创建处理登录的 Servlet (LoginServlet.java)

当用户填写完表单并点击登录后,数据会提交到这个 Servlet,这里的核心逻辑是验证码校验

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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 设置请求和响应编码
        req.setCharacterEncoding("UTF-8");
        resp.setContentType("text/html;charset=UTF-8");
        // 2. 获取用户输入
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String userCode = req.getParameter("userCode");
        // 3. 从 Session 中获取服务器生成的验证码
        HttpSession session = req.getSession();
        String serverCode = (String) session.getAttribute("imageCode");
        // 4. 验证码校验 (不区分大小写)
        if (serverCode == null || !serverCode.equalsIgnoreCase(userCode)) {
            // 验证码错误,返回登录页面并提示
            req.setAttribute("errorMsg", "验证码错误!");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
            return; // 终止后续操作
        }
        // 5. 验证码正确,继续验证用户名和密码
        // ... 这里是你的业务逻辑,比如查询数据库 ...
        if ("admin".equals(username) && "123456".equals(password)) {
            resp.getWriter().println("<h1>登录成功!欢迎 " + username + "</h1>");
        } else {
            req.setAttribute("errorMsg", "用户名或密码错误!");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
        }
    }
}

LoginServlet 关键点说明:

Java Servlet验证码如何实现与校验?-图2
(图片来源网络,侵删)
  • session.getAttribute("imageCode"): 从 Session 中取出我们之前存入的验证码。
  • equalsIgnoreCase(userCode): 使用 equalsIgnoreCase 进行比较,这样用户输入时就不区分大小写了,提升了用户体验。
  • 校验失败后使用 forward: 验证码错误时,通常使用 request.getRequestDispatcher("login.jsp").forward(req, resp) 将请求转发回登录页面,而不是重定向 (redirect),这样可以带上错误信息(通过 request.setAttribute)并保留用户之前输入的用户名和密码(如果表单中有设置 value 属性)。
  • 验证码使用后应立即失效: 在一个标准的实现中,一旦验证码被校验(无论成功与否),就应该立即从 Session 中移除或标记为已使用,以防止被重复使用,可以在校验成功后加上 session.removeAttribute("imageCode");

总结与注意事项

  1. 安全性: java.awt 生成的验证码相对简单,容易被 OCR(光学字符识别)程序识别,对于安全性要求极高的场景,可以考虑:

    • 使用更复杂的干扰线和扭曲效果。
    • 引入第三方库,如 Kaptcha,它提供了丰富的、可配置的验证码样式。
    • 实现滑动验证码或点选验证码。
  2. Session 管理: 验证码必须存储在 Session 中,确保你的应用正确配置了 Session。

  3. 字符集: 在 CHARACTERS 字符串中,我移除了容易混淆的字符,如 0/O, 1/I/l 等,这可以减少用户输入错误。

  4. 依赖: 这个实现完全基于 JDK 标准库,无需任何额外依赖,非常适合快速集成和原型开发。

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

通过以上三个步骤,你就完整地实现了一个在 Java Web 应用中生成和验证验证码的功能。

分享:
扫描分享到社交APP
上一篇
下一篇