首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Web 验证码实现全攻略:Servlet 绘图、Session 校验、前端交互

     編輯:Java 相關知識

验证码核心作用

防止表单恶意提交、暴力破解、机器人爬虫、刷接口攻击。核心流程:生成随机码 → 存 Session → 绘制图片 → 响应输出 → 前端展示 → 用户输入 → 后端校验。

后端实现:VerifyCodeServlet 完整代码

@WebServlet("/VerifyCodeServlet")
public class VerifyCodeServlet extends HttpServlet {
    // 配置参数(可在 web.xml init-param 覆盖)
    private int width = 120;
    private int height = 32;
    private int codeCount = 4;
    private int fontHeight;
    private int codeX, codeY;
    
    // 字符集:去除易混淆字符 (0/O, 1/I/l)
    private char[] codeSequence = {
        'A','B','C','D','E','F','G','H','J','K','M','N','P','Q','R','S','T','U','V','W','X','Y','Z',
        '2','3','4','5','6','7','8','9'
    };

    @Override
    public void init() throws ServletException {
        // 从 web.xml 读取配置
        String w = getInitParameter("width");
        String h = getInitParameter("height");
        String c = getInitParameter("codeCount");
        if (w != null) width = Integer.parseInt(w);
        if (h != null) height = Integer.parseInt(h);
        if (c != null) codeCount = Integer.parseInt(c);
        
        // 计算绘制坐标
        codeX = (width - 4) / (codeCount + 1);
        fontHeight = height - 10;
        codeY = height - 7;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) 
            throws ServletException, IOException {
        // 1. 创建画布
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        Random random = new Random();

        // 2. 背景填充
        g.setColor(Color.LIGHT_GRAY);
        g.fillRect(0, 0, width, height);

        // 3. 字体设置
        Font font = new Font("Fixedsys", Font.PLAIN, fontHeight);
        g.setFont(font);

        // 4. 干扰线(16 条)
        g.setColor(Color.GRAY);
        for (int i = 0; i < 16; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 5. 绘制验证码字符
        StringBuilder randomCode = new StringBuilder();
        for (int i = 0; i < codeCount; i++) {
            String ch = String.valueOf(codeSequence[random.nextInt(codeSequence.length)]);
            // 随机颜色
            g.setColor(new Color(random.nextInt(255), random.nextInt(255), random.nextInt(255)));
            g.drawString(ch, (i + 1) * codeX, codeY);
            randomCode.append(ch);
        }

        // 6. 存入 Session(大写统一,校验时忽略大小写)
        HttpSession session = req.getSession();
        session.setAttribute("validateCode", randomCode.toString().toUpperCase());

        // 7. 禁止缓存
        resp.setHeader("Pragma", "No-cache");
        resp.setHeader("Cache-Control", "no-cache");
        resp.setDateHeader("Expires", 0);
        resp.setContentType("image/jpeg");

        // 8. 输出图片流
        try (ServletOutputStream out = resp.getOutputStream()) {
            ImageIO.write(image, "JPEG", out);
        }
    }
}

web.xml 配置(可选,Servlet 3.0+ 注解已替代)

<servlet>
    <servlet-name>VerifyCodeServlet</servlet-name>
    <servlet-class>com.servlet.VerifyCodeServlet</servlet-class>
    <init-param><param-name>width</param-name><param-value>120</param-value></init-param>
    <init-param><param-name>height</param-name><param-value>32</param-value></init-param>
    <init-param><param-name>codeCount</param-name><param-value>4</param-value></init-param>
</servlet>
<servlet-mapping>
    <servlet-name>VerifyCodeServlet</servlet-name>
    <url-pattern>/VerifyCodeServlet</url-pattern>
</servlet-mapping>

前端页面:登录表单 + 验证码交互

<%@ page pageEncoding="UTF-8"%>
<html><head><title>用户登录</title></head><body>
<form action="login.action" method="post">
    用户名: <input name="username" /><br/>
    密码: <input name="password" type="password" /><br/>
    验证码: <input name="verifyCode" maxlength="4" style="width:60px" />
    <img id="captchaImg" src="VerifyCodeServlet" 
         align="absmiddle" title="看不清?点击刷新" style="cursor:pointer" />
    <br/>
    <input type="submit" value="登录" />
</form>

<script>
// 点击图片刷新验证码(加随机参数防浏览器缓存)
document.getElementById('captchaImg').onclick = function() {
    this.src = 'VerifyCodeServlet?' + Math.random();
};
</script>
</body></html>

后端校验逻辑(Action/Controller 中)

public String login() {
    String inputCode = request.getParameter("verifyCode");
    String sessionCode = (String) session.getAttribute("validateCode");
    
    if (inputCode == null || sessionCode == null) {
        addFieldError("verifyCode", "验证码不能为空");
        return INPUT;
    }
    
    // 忽略大小写校验
    if (!inputCode.equalsIgnoreCase(sessionCode)) {
        addFieldError("verifyCode", "验证码错误");
        return INPUT;
    }
    
    // 校验通过,移除验证码防止重用
    session.removeAttribute("validateCode");
    
    // 继续业务校验...
    return SUCCESS;
}

进阶增强方案

现代替代:Google reCAPTCHA v3 / hCaptcha

<!-- 前端引入 -->
<script src="https://www.google.com/recaptcha/api.js?render=your_site_key"></script>
<script>
grecaptcha.ready(function() {
    grecaptcha.execute('your_site_key', {action: 'login'}).then(function(token) {
        document.getElementById('recaptchaToken').value = token;
    });
});
</script>

<!-- 表单隐藏域 -->
<input type="hidden" name="recaptchaToken" id="recaptchaToken" />

后端验证:HTTP POST 到 https://www.google.com/recaptcha/api/siteverify,解析 JSON 响应 score > 0.5 判定为人类。

总结

手写 Servlet 验证码适合学习原理、简单项目。生产环境建议:引入成熟验证码服务(reCAPTCHA/hCaptcha/极验),兼顾安全性、用户体验、运维成本。核心原则:生成存 Session/Redis、校验即销毁、防缓存刷新、忽略大小写。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
痛点解决方案
图片验证码用户体验差滑块拼图/点选文字/行为验证(如极验、网易易盾)
分布式集群 Session 不共享Redis 存储验证码,Key: captcha:{uuid}, TTL 5分钟
高并发图片生成性能瓶颈预生成验证码池、异步生成、CDN 缓存静态资源
安全性不足(OCR 识别)增加扭曲、干扰点、字体随机、中文汉字验证码