最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用JSP+JavaScript實(shí)現(xiàn)驗(yàn)證碼登錄功能

 更新時(shí)間:2026年01月07日 09:48:17   作者:澾慟  
在現(xiàn)代?Web?應(yīng)用中,用戶身份驗(yàn)證是安全體系的第一道防線,然而,單純依賴用戶名和密碼的登錄機(jī)制早已不堪一擊,一個(gè)看似簡單的驗(yàn)證碼功能,實(shí)則承載著抵御機(jī)器流量的重要職責(zé),今天就來分享如何使用JSP+JavaScript實(shí)現(xiàn)驗(yàn)證碼登錄功能,需要的朋友可以參考下

引言

在現(xiàn)代 Web 應(yīng)用中,用戶身份驗(yàn)證是安全體系的第一道防線。然而,單純依賴用戶名和密碼的登錄機(jī)制早已不堪一擊——自動化腳本、暴力 破解、爬蟲攻擊層出不窮。一個(gè)看似簡單的“驗(yàn)證碼”功能,實(shí)則承載著抵御機(jī)器流量的重要職責(zé)。

最近我在重構(gòu)一個(gè)傳統(tǒng) Java Web 項(xiàng)目時(shí),重新審視了這一經(jīng)典安全組件的設(shè)計(jì)與實(shí)現(xiàn)。今天就來分享如何使用 JSP + Servlet + JavaScript 技術(shù)棧,從零搭建一個(gè)結(jié)構(gòu)清晰、交互流暢、具備基礎(chǔ)防攻擊能力的驗(yàn)證碼登錄系統(tǒng)。

整個(gè)過程并不復(fù)雜,但涉及前后端協(xié)作、會話管理、圖像生成等多個(gè)關(guān)鍵點(diǎn)。我們將一步步完成:動態(tài)驗(yàn)證碼圖片生成 → 前端展示與刷新 → 表單提交 → 后臺校驗(yàn)全流程,并在最后探討其可擴(kuò)展性與未來演進(jìn)方向。

動態(tài)驗(yàn)證碼是如何工作的?

驗(yàn)證碼的核心邏輯其實(shí)很樸素:服務(wù)器生成一段隨機(jī)字符,繪制成干擾性強(qiáng)的圖片供人識別,同時(shí)將原始值存儲在當(dāng)前用戶的 Session 中;當(dāng)用戶輸入后,后臺比對輸入值與 Session 中的記錄是否一致。

這個(gè)過程中有幾個(gè)關(guān)鍵設(shè)計(jì)必須注意:

  • 防止緩存:瀏覽器不能緩存驗(yàn)證碼圖片,否則點(diǎn)擊刷新無效
  • 時(shí)效控制:驗(yàn)證碼不應(yīng)永久有效,通常設(shè)置為60秒過期
  • 一次消費(fèi):成功驗(yàn)證后應(yīng)立即清除,防止重放攻擊
  • 大小寫處理:建議統(tǒng)一轉(zhuǎn)小寫或大寫進(jìn)行比較,提升用戶體驗(yàn)

接下來我們就通過三個(gè)核心模塊來落地這套機(jī)制。

驗(yàn)證碼圖像生成(Servlet)

我們創(chuàng)建一個(gè) VerifyCodeServlet,專門負(fù)責(zé)生成并輸出 JPEG 圖像。它被綁定到 /captcha 路徑,前端只需 <img src="captcha"> 即可獲取最新驗(yàn)證碼。

package com.example.captcha;

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("/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();
    }
}

這段代碼有幾個(gè)值得注意的細(xì)節(jié):

  • 使用 @WebServlet 注解簡化部署配置;
  • 設(shè)置 HTTP 頭禁止緩存,確保每次請求都能拿到新圖;
  • 干擾線數(shù)量適中(50條),避免影響正常識別;
  • 字符集排除了易混淆的 O/0I/l/1,降低用戶輸錯(cuò)概率;
  • 顏色隨機(jī)化增強(qiáng) OCR 難度,但不過度花哨以免影響可讀性;
  • 最終驗(yàn)證碼轉(zhuǎn)為小寫存入 Session,方便后續(xù)忽略大小寫校驗(yàn)。

小技巧:如果你希望支持中文提示詞渲染,可以考慮引入 TTF 字體文件并調(diào)用 g.setFont() 加載自定義字體,這對國際化項(xiàng)目尤其有用。

登錄頁面設(shè)計(jì)(Login.jsp)

前端頁面采用純 JSP 編寫,結(jié)合內(nèi)聯(lián) CSS 和輕量 JS 實(shí)現(xiàn)良好視覺效果與交互體驗(yàn)。

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

<!DOCTYPE html>
<html>
<head>
    <base href="<%=basePath%>" rel="external nofollow" >
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>用戶登錄</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f4f6f9;
            padding: 50px;
            text-align: center;
        }
        .login-box {
            width: 400px;
            margin: 0 auto;
            background: white;
            border-radius: 10px;
            padding: 30px;
            box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
        }
        input[type="text"], input[type="password"] {
            width: 100%;
            padding: 10px;
            margin: 10px 0;
            border: 1px solid #ccc;
            border-radius: 5px;
            box-sizing: border-box;
        }
        img {
            cursor: pointer;
            border: 1px solid #ddd;
            border-radius: 5px;
            margin-top: 10px;
        }
        input[type="submit"] {
            background-color: #007bff;
            color: white;
            padding: 12px 30px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
        }
        input[type="submit"]:hover {
            background-color: #0056b3;
        }
    </style>
</head>
<body>
<div class="login-box">
    <h2>用戶登錄</h2>
    <form action="userLogin" method="post">
        <label>用戶名:</label>

        <input type="text" name="username" required>


        <label>密碼:</label>

        <input type="password" name="password" required>


        <label>驗(yàn)證碼:</label>

        <input type="text" name="verifyCode" maxlength="4" required>

        <img src="captcha" id="captchaImg" alt="驗(yàn)證碼">

        <small>點(diǎn)擊圖片更換驗(yàn)證碼</small>


        <input type="submit" value="登錄">
    </form>
</div>

<script type="text/javascript">
    window.onload = function () {
        const img = document.getElementById("captchaImg");
        img.onclick = function () {
            this.src = "captcha?timestamp=" + new Date().getTime();
        };
    };
</script>
</body>
</html>

這里的關(guān)鍵在于驗(yàn)證碼圖片的動態(tài)加載機(jī)制:

  • 初始 <img src="captcha"> 請求生成第一張圖;
  • 用戶點(diǎn)擊圖片時(shí),JS 修改 src 屬性,附加時(shí)間戳參數(shù)(如 ?timestamp=171234567890);
  • 由于 URL 發(fā)生變化,瀏覽器不會使用緩存,從而觸發(fā) Servlet 重新生成新驗(yàn)證碼。

這種做法簡單高效,無需額外 AJAX 請求或 DOM 操作,非常適合輕量級場景。

登錄邏輯處理(UserLoginServlet.java)

最后是登錄表單的接收與驗(yàn)證邏輯:

package com.example.captcha;

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 java.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("<script>alert('驗(yàn)證碼錯(cuò)誤!'); history.back();</script>");
            return;
        }

        if ("admin".equals(username) && "123456".equals(password)) {
            out.println("<h2 style='color:green;'>? 登錄成功!歡迎回來," + username + "</h2>");
            session.removeAttribute("verify_code"); // 防止重復(fù)使用
        } else {
            out.println("<script>alert('用戶名或密碼錯(cuò)誤!'); history.back();</script>");
        }
    }
}

幾點(diǎn)說明:

  • 輸入值統(tǒng)一轉(zhuǎn)為小寫再比對,避免因大小寫導(dǎo)致失?。?/li>
  • 校驗(yàn)失敗直接彈窗并返回上一頁,保持上下文;
  • 成功登錄后主動移除 Session 中的驗(yàn)證碼,杜絕二次利用;
  • 當(dāng)前示例使用硬編碼賬戶,實(shí)際項(xiàng)目中應(yīng)連接數(shù)據(jù)庫查詢用戶信息。

安全提醒:生產(chǎn)環(huán)境中應(yīng)使用加密存儲密碼(如 BCrypt),并通過 PreparedStatements 防止 SQL 注入。

可行的優(yōu)化方向

雖然上述方案已能滿足基本需求,但在真實(shí)項(xiàng)目中還可以進(jìn)一步增強(qiáng):

優(yōu)化項(xiàng)實(shí)現(xiàn)方式
添加噪點(diǎn)在圖像上繪制隨機(jī)像素點(diǎn),增加 OCR 識別難度
設(shè)置過期時(shí)間web.xml 中設(shè)置 Session 超時(shí)(如 <session-config><timeout>1</timeout></session-config>
AJAX 提交改用異步提交,避免整頁刷新,提升 UX
返回 JSON后端返回結(jié)構(gòu)化數(shù)據(jù),便于前后端分離架構(gòu)集成
滑動/點(diǎn)選驗(yàn)證碼引入更復(fù)雜的交互式驗(yàn)證機(jī)制,適合高安全要求場景

例如,你可以將當(dāng)前的同步表單提交改為 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 響應(yīng),即可實(shí)現(xiàn)無刷新登錄體驗(yàn)。

寫在最后:傳統(tǒng)驗(yàn)證碼的未來在哪里?

我們今天實(shí)現(xiàn)的是典型的“圖像+文本”型驗(yàn)證碼,屬于 CAPTCHA 的初級形態(tài)。隨著 AI 視覺技術(shù)的發(fā)展,這類簡單干擾圖像正變得越來越容易被破解。

但反過來看,AI 也在催生新一代智能驗(yàn)證方式。比如阿里 recently 開源的 Z-Image-ComfyUI 模型,具備強(qiáng)大的文生圖能力和語義理解水平,完全可以用來構(gòu)建“智能圖形挑戰(zhàn)”:

  • “請選出包含雨傘的圖片”
  • “根據(jù)描述生成指定圖案”
  • “判斷兩幅圖是否表達(dá)相同含義”

這些基于語義理解的任務(wù),機(jī)器難以批量偽造,而人類卻能輕松應(yīng)對。未來的驗(yàn)證碼可能不再是“你是不是人”,而是“你有沒有人類的理解力”。

當(dāng)然,在絕大多數(shù)普通業(yè)務(wù)系統(tǒng)中,傳統(tǒng)的字符驗(yàn)證碼依然夠用且成本低廉。只要合理設(shè)計(jì)、及時(shí)更新策略,它仍然是性價(jià)比極高的安全防護(hù)手段。

如果你正在做一個(gè)學(xué)生管理系統(tǒng)、內(nèi)部辦公平臺或小型 CMS,本文這套 JSP + Servlet 方案完全足夠快速上線;
如果你關(guān)注前沿趨勢,不妨嘗試將 Z-Image 這類 AIGC 工具接入驗(yàn)證流程,探索下一代人機(jī)識別的可能性。

技術(shù)沒有高低之分,只有適不適合。守住底線安全,同時(shí)保持對未來的敏感,這才是開發(fā)者應(yīng)有的姿態(tài)。

以上就是使用JSP+JavaScript實(shí)現(xiàn)驗(yàn)證碼登錄功能的詳細(xì)內(nèi)容,更多關(guān)于JSP JavaScript驗(yàn)證碼登錄的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論

灵宝市| 阿拉善盟| 文安县| 安泽县| 永安市| 云浮市| 冷水江市| 醴陵市| 开平市| 沙坪坝区| 黄龙县| 海淀区| 肥东县| 沧州市| 子长县| 得荣县| 汉中市| 肥城市| 青冈县| 吴堡县| 高要市| 永和县| 双桥区| 珠海市| 高淳县| 泾川县| 邵阳市| 花莲市| 宜章县| 巍山| 东平县| 巩留县| 鸡东县| 师宗县| 建德市| 荔浦县| 卓资县| 湖口县| 来宾市| 纳雍县| 金湖县|