杰瑞科技汇

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

核心思想

验证码的核心目的,是区分“人”和“机器”(如自动化脚本),其工作流程如下:

  1. 服务端生成:服务器生成一个随机字符串(验证码)。
  2. 服务端存储:服务器将这个随机字符串存入 HttpSession 中。
  3. 服务端展示:服务器将这个随机字符串转换成一张图片(或其他形式,如短信、语音),并返回给浏览器。
  4. 用户输入:用户在页面上看到图片,并将图片中的字符输入到表单中。
  5. 用户提交:用户提交表单,表单中包含了用户输入的验证码。
  6. 服务端验证:服务器从 HttpSession 中取出之前存储的验证码,与用户提交的验证码进行比较。
    • 如果一致,则验证通过,继续处理业务逻辑(如登录、注册)。
    • 如果不一致,则验证失败,提示用户“验证码错误”。

关键点:验证码的生命周期是“一次性的”,一旦验证成功或失败,服务器就应该立即将 Session 中的验证码清除,防止同一个验证码被重复使用。


实现步骤(以 Servlet + JSP 为例)

我们将分步实现一个完整的登录验证码流程。

第1步:创建 Maven Web 项目

确保你的项目是一个 Maven Web 项目,并添加 javax.servlet-api 依赖。

<dependencies>
    <!-- Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
</dependencies>

第2步:创建验证码生成工具类 VerifyCodeUtils.java

这个类负责生成随机字符串和将其绘制成图片。

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Random;
public class VerifyCodeUtils {
    // 使用 Alphanumeric 类型的字符
    private static final char[] chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
    private static final int WIDTH = 100;
    private static final int HEIGHT = 40;
    private static final int CODE_LENGTH = 4; // 验证码长度
    private static final int LINES = 5; // 干扰线数量
    /**
     * 生成随机验证码字符串
     * @return 验证码字符串
     */
    public static String generateVerifyCode() {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < CODE_LENGTH; i++) {
            sb.append(chars[random.nextInt(chars.length)]);
        }
        return sb.toString();
    }
    /**
     * 生成验证码图片
     * @param verifyCode 验证码字符串
     * @param out 输出流
     * @throws IOException
     */
    public static void outputImage(String verifyCode, OutputStream out) throws IOException {
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2d = image.createGraphics();
        // 设置背景色
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, WIDTH, HEIGHT);
        // 设置字体
        g2d.setFont(new Font("Arial", Font.BOLD, 20));
        Random random = new Random();
        // 绘制验证码
        for (int i = 0; i < verifyCode.length(); i++) {
            g2d.setColor(getRandomColor(random));
            int x = 10 + i * 22;
            int y = 25 + random.nextInt(5);
            g2d.drawString(String.valueOf(verifyCode.charAt(i)), x, y);
        }
        // 绘制干扰线
        for (int i = 0; i < LINES; i++) {
            g2d.setColor(getRandomColor(random));
            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);
        }
        g2d.dispose();
        ImageIO.write(image, "JPEG", out);
    }
    private static Color getRandomColor(Random random) {
        int r = random.nextInt(256);
        int g = random.nextInt(256);
        int b = random.nextInt(256);
        return new Color(r, g, b);
    }
}

第3步:创建 Servlet 生成并发送验证码 (VerifyCodeServlet.java)

这个 Servlet 负责调用工具类生成验证码,并将其存入 Session,最后将图片响应给浏览器。

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("/verifyCode")
public class VerifyCodeServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 1. 生成验证码
        String verifyCode = VerifyCodeUtils.generateVerifyCode();
        // 2. 将验证码存入 Session
        HttpSession session = req.getSession();
        session.setAttribute("verifyCode", verifyCode);
        // 3. 设置响应内容为图片
        resp.setContentType("image/jpeg");
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        // 4. 将图片写给浏览器
        VerifyCodeUtils.outputImage(verifyCode, resp.getOutputStream());
    }
}

第4步:创建登录页面 (login.jsp)

这个 JSP 页面包含一个图片标签来显示验证码,以及一个刷新验证码的链接。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>登录页面</title>
    <script>
        function refreshCode() {
            // 获取img标签
            var img = document.getElementById("verifyCodeImg");
            // 加上时间戳,防止浏览器缓存图片
            img.src = "verifyCode?" + new Date().getTime();
        }
    </script>
</head>
<body>
    <h2>用户登录</h2>
    <form action="login" method="post">
        用户名: <input type="text" name="username"><br>
        密码:   <input type="password" name="password"><br>
        验证码: <input type="text" name="verifyCodeInput">
        <!-- 
          src 指向我们创建的验证码 Servlet
          onclick="refreshCode()" 点击图片时调用刷新函数
        -->
        <img id="verifyCodeImg" src="verifyCode" onclick="refreshCode()" title="点击刷新验证码"><br>
        <input type="submit" value="登录">
    </form>
    <%-- 显示错误信息 --%>
    <font color="red">${requestScope.errorMsg}</font>
</body>
</html>

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

这个 Servlet 接收登录请求,并从 Session 中取出验证码进行比对。

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 {
        req.setCharacterEncoding("UTF-8");
        // 1. 获取用户输入
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        String userInputCode = req.getParameter("verifyCodeInput");
        // 2. 从 Session 中获取正确的验证码
        HttpSession session = req.getSession();
        String sessionCode = (String) session.getAttribute("verifyCode");
        // 3. 验证码校验(注意大小写,这里我们统一转成大写比较)
        if (sessionCode == null || !sessionCode.equalsIgnoreCase(userInputCode)) {
            // 验证失败,将错误信息存入 request,并转发回登录页
            req.setAttribute("errorMsg", "验证码错误!");
            req.getRequestDispatcher("login.jsp").forward(req, resp);
            return; // 终止后续流程
        }
        // 4. 验证码正确,清除 Session 中的验证码(非常重要!)
        session.removeAttribute("verifyCode");
        // 5. 进行后续的业务逻辑(如用户名密码校验)
        // ... 这里省略用户名密码校验逻辑 ...
        System.out.println("验证码通过,正在进行用户名密码校验...");
        // 假设校验成功
        resp.setContentType("text/html;charset=UTF-8");
        resp.getWriter().write("<h1>登录成功!欢迎您," + username + "</h1>");
    }
}

关键点解析

  1. session.setAttribute("verifyCode", verifyCode)

    • 这是核心步骤,我们将生成的验证码字符串作为值,"verifyCode" 作为键,存入 HttpSession 对象中。HttpSession 是每个用户独有的,所以不同用户的验证码不会混淆。
  2. <img src="verifyCode">

    • 浏览器会向 VerifyCodeServlet 发送一个 GET 请求,VerifyCodeServlet 生成图片并返回,浏览器接收到二进制图片数据后,就会将其显示在 <img> 标签内。
  3. onclick="refreshCode()"img.src = "verifyCode?" + new Date().getTime()

    为了防止浏览器缓存图片导致刷新无效,我们在请求 URL 后面加了一个时间戳,每次点击,URL 都会变化,浏览器就会重新请求服务器,生成新的验证码图片。

  4. session.removeAttribute("verifyCode")

    • 这是非常关键的一步,在验证成功后,必须立即从 Session 中移除验证码,这可以防止“验证码重放攻击”,即攻击者截获一次成功的请求后,重放该请求来绕过验证码。
  5. 不区分大小写

    • 人类用户有时会分不清大小写(如 O0),为了提升用户体验,通常在验证时不区分大小写,我们可以将 Session 中的码和用户输入的码都转换成大写或小写后再比较。

总结与扩展

步骤 操作 相关代码/技术
生成 服务端创建随机字符串和图片 VerifyCodeUtils, BufferedImage, Graphics2D
存储 将字符串存入用户专属的 Session session.setAttribute("verifyCode", code)
展示 将图片响应给浏览器,<img> 标签显示 resp.setContentType("image/jpeg")
输入 用户在表单中输入字符 <input type="text" name="verifyCodeInput">
验证 从 Session 取出码与用户输入比对 session.getAttribute("verifyCode")
清理 验证后立即从 Session 移除码 session.removeAttribute("verifyCode")

扩展思考

  • 其他验证码类型:除了图片验证码,还有短信验证码、邮件验证码、语音验证码等,它们的原理类似,只是“展示”和“输入”的方式不同,短信验证码是存入 Session,然后通过短信网关发送到用户手机,用户在页面上输入手机号和收到的验证码进行验证。
  • 使用第三方库:在实际项目中,可以使用更成熟的库来生成验证码,Google 的 Kaptcha,它提供了更多定制选项(如扭曲、噪点等),使用起来也更方便。
  • 安全性:验证码不是万能的,它是防御自动化攻击的第一道防线,结合 IP 限制、请求频率限制(Rate Limiting)等手段,可以构建更强大的安全体系。
分享:
扫描分享到社交APP
上一篇
下一篇