一、实验目的

        掌握JSP的内置对象技术以及表单验证在服务器端的实现技术

二、实验内容与设计思想

        编写网站的登录页面,要求能保存用户登录成功时输入的用户名及密码信息,当同一用户 3天内再次登录网站首页时,能自动读取之前保存的用户信息判断身份是否合法,如果合法自动完成登录。如果当同一用户超过3天再次登录网站首页时,则需要重新输入用户名及密码进行身份验证,通过验证方能登录并显示欢迎信息。否则提示:身份验证失败,必须重新输入。

三、  实验使用环境

操作系统:  Microsoft Windows 10

编程环境:JDK1.8、TOMCAT 8.0 、DreamWeaver CS5或Eclipse Developer for JavaEE

四、 核心代码实现

1. index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.net.URLDecoder" %>
<html>
<head><title>首页</title></head>
<body>
<%
    // 获取所有 Cookie
    Cookie[] cookies = request.getCookies();
    String username = null;
    String password = null;
    long loginTime = 0;

    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if ("user".equals(cookie.getName())) {
                // Cookie 格式:username:password:timestamp
                String[] parts = cookie.getValue().split(":");
                if (parts.length == 3) {
                    username = URLDecoder.decode(parts[0], "UTF-8");
                    password = URLDecoder.decode(parts[1], "UTF-8");
                    loginTime = Long.parseLong(parts[2]);
                }
            }
        }
    }

    // 如果存在 Cookie 且未过期(3天内),自动登录
    if (username != null && password != null) {
        long currentTime = System.currentTimeMillis();
        long threeDaysInMillis = 3 * 24 * 60 * 60 * 1000; // 3天

        if ((currentTime - loginTime) <= threeDaysInMillis) {
            // 自动登录成功,跳转到欢迎页
            response.sendRedirect("welcome.jsp?username=" + java.net.URLEncoder.encode(username, "UTF-8"));
        } else {
            // 超过3天,清除旧 Cookie,显示登录页
            Cookie clearCookie = new Cookie("user", "");
            clearCookie.setMaxAge(0); // 删除 Cookie
            response.addCookie(clearCookie);
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    } else {
        // 没有 Cookie,显示登录页
        request.getRequestDispatcher("login.jsp").forward(request, response);
    }
%>
</body>
</html>

2. login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>安全登录中心</title>
    <!-- 引入 Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- 引入图标库 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">

    <style>
        /* 1. 高级感渐变背景 */
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: "Microsoft YaHei", sans-serif;
        }

        /* 2. 卡片玻璃拟态效果(可选,这里用纯白更清晰,加一点阴影) */
        .login-card {
            border: none;
            border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.1);
            overflow: hidden; /* 防止内容溢出圆角 */
            background: #ffffff;
        }

        /* 3. 标题装饰 */
        .card-header {
            background: #fff;
            border-bottom: 1px solid #f0f0f0;
            padding: 2rem 2rem 1rem;
            text-align: center;
        }

        .card-header h3 {
            font-weight: 700;
            color: #333;
            margin: 0;
            letter-spacing: 1px;
        }

        .card-body {
            padding: 2rem;
        }

        /* 4. 按钮渐变与动画 */
        .btn-primary {
            background: linear-gradient(to right, #667eea, #764ba2);
            border: none;
            padding: 10px 0;
            font-weight: 600;
            letter-spacing: 1px;
            transition: all 0.3s ease;
        }

        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
        }

        /* 5. 输入框样式微调 */
        .form-control:focus {
            border-color: #667eea;
            box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
        }

        /* 6. 提示框美化 */
        .alert-custom {
            border-radius: 10px;
            font-size: 0.9rem;
            animation: slideDown 0.5s ease-out;
        }

        @keyframes slideDown {
            from { opacity: 0; transform: translateY(-10px); }
            to { opacity: 1; transform: translateY(0); }
        }
    </style>
</head>
<body>

<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-4 col-lg-4">
            <div class="card login-card">

                <!-- 卡片头部:标题 -->
                <div class="card-header">
                    <h3><i class="bi bi-shield-lock-fill text-primary"></i> 安全登录</h3>
                </div>

                <div class="card-body">

                    <!-- 7. 动态消息提示区域 -->
                    <%
                        // 获取后端传来的错误消息或提示
                        String errorMsg = (String) request.getAttribute("errorMsg");
                        String successMsg = (String) request.getAttribute("successMsg"); // 假设你可能会有成功提示
                    %>

                    <!-- 如果有错误消息,显示红色警告 -->
                    <% if (errorMsg != null) { %>
                        <div class="alert alert-danger alert-dismissible fade show alert-custom" role="alert">
                            <i class="bi bi-exclamation-triangle-fill"></i> &nbsp;
                            <%= errorMsg %>
                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                        </div>
                    <% } %>

                    <!-- 如果有成功消息(例如注册成功),显示绿色/蓝色提示 -->
                    <% if (successMsg != null) { %>
                        <div class="alert alert-success alert-dismissible fade show alert-custom" role="alert">
                            <i class="bi bi-check-circle-fill"></i> &nbsp;
                            <%= successMsg %>
                            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
                        </div>
                    <% } %>

                    <!-- 8. 登录表单 -->
                    <form action="LoginServlet" method="post">

                        <!-- 用户名输入 -->
                        <div class="form-floating mb-3">
                            <input type="text" class="form-control" id="username" name="username" placeholder="请输入用户名" required>
                            <label for="username"><i class="bi bi-person"></i> 用户名</label>
                        </div>

                        <!-- 密码输入 -->
                        <div class="form-floating mb-4">
                            <input type="password" class="form-control" id="password" name="password" placeholder="请输入密码" required>
                            <label for="password"><i class="bi bi-key"></i> 密码</label>
                        </div>

                        <!-- 登录按钮 -->
                        <div class="d-grid">
                            <button type="submit" class="btn btn-primary btn-lg">
                                <i class="bi bi-box-arrow-in-right"></i> 立即登录
                            </button>
                        </div>
                    </form>

                </div>
            </div>
        </div>
    </div>
</div>

<!-- 引入 Bootstrap JS (用于关闭提示框等交互) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

3. LoginServlet.java

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.net.URLEncoder;
import javax.servlet.annotation.WebServlet;

@WebServlet("/LoginServlet")
public class LoginServlet extends HttpServlet {

    /**
     * 新增:处理 GET 请求
     * 当用户直接在浏览器地址栏输入 URL 时,会自动跳转到登录页,避免 405 错误。
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 重定向回登录页面
        response.sendRedirect("login.jsp");
    }

    /**
     * 处理 POST 请求(表单提交)
     * 逻辑:记住第一次输入的密码,第二次进行比对。
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        // 设置编码
        request.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");

        // 获取表单数据
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 获取 Session 对象
        HttpSession session = request.getSession();

        // 从 Session 中获取之前保存的数据
        String registeredPassword = (String) session.getAttribute("registeredPassword");
        String sessionUsername = (String) session.getAttribute("username");

        // --- 核心修改:检测是否切换了账号 ---
        // 如果 Session 里有用户名,但和当前输入的用户名不一样,说明换人了
        if (sessionUsername != null && !sessionUsername.equals(username)) {
            // 强制重置 Session,清除旧账号的密码,让新账号重新开始
            session.removeAttribute("registeredPassword");
            session.removeAttribute("username");
            // 更新标记,避免后续逻辑混乱
            registeredPassword = null;
        }

        // 核心逻辑:判断是“第一次输入(注册)”还是“第二次输入(登录)”
        if (registeredPassword == null) {
            // --- 情况 1:第一次输入(注册) ---
            // 此时 Session 里没有密码,我们将当前输入的密码存入 Session

            // 1. 保存密码到 Session
            session.setAttribute("registeredPassword", password);

            // 2. 保存用户名到 Session
            session.setAttribute("username", username);

            // 3. 提示用户注册成功,请再次输入
            request.setAttribute("errorMsg", "注册成功!请在下方再次输入密码以完成登录。");
            request.getRequestDispatcher("login.jsp").forward(request, response);

        } else {
            // --- 情况 2:第二次输入(登录) ---
            // 此时 Session 里已经有密码了,我们需要进行比对

            if (password.equals(registeredPassword)) {
                // 密码比对一致,登录成功

                // 1. 获取当前时间戳
                long currentTime = System.currentTimeMillis();

                // 2. 创建 Cookie
                String cookieValue = URLEncoder.encode(username, "UTF-8") + ":" +
                        URLEncoder.encode(password, "UTF-8") + ":" +
                        currentTime;

                Cookie userCookie = new Cookie("user", cookieValue);

                // 3. 设置 Cookie 有效期为 3 天
                userCookie.setMaxAge(3 * 24 * 60 * 60);

                // 4. 设置路径
                userCookie.setPath("/");

                // 5. 添加 Cookie
                response.addCookie(userCookie);

                // 6. 跳转
                response.sendRedirect("welcome.jsp?username=" + URLEncoder.encode(username, "UTF-8"));

            } else {
                // 密码不一致,登录失败
                request.setAttribute("errorMsg", "密码与注册时不一致,登录失败。");
                request.getRequestDispatcher("login.jsp").forward(request, response);
            }
        }
    }
}

4.welcome.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    // 保持原有的逻辑判断:如果没有用户名,就踢回登录页
    String username = request.getParameter("username");
    if (username == null || username.isEmpty()) {
        response.sendRedirect("login.jsp"); // 注意:根据你之前的代码,登录页应该是 login.jsp
        return;
    }
%>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>欢迎回来</title>
    <!-- 引入 Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- 引入图标库 (Bootstrap Icons) -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css" rel="stylesheet">
    
    <style>
        /* 保持与登录页一致的渐变背景 */
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            font-family: "Microsoft YaHei", sans-serif;
        }
        
        /* 卡片样式优化 */
        .welcome-card {
            border: none;
            border-radius: 20px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.2);
            text-align: center;
            padding: 3rem 2rem;
            background: #ffffff;
            max-width: 400px;
            width: 100%;
            animation: fadeIn 0.8s ease-out; /* 添加淡入动画 */
        }

        /* 图标样式 */
        .avatar-icon {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            width: 80px;
            height: 80px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 40px;
            margin: 0 auto 1.5rem auto;
            box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4);
        }

        /* 用户名高亮样式 */
        .user-highlight {
            background: linear-gradient(120deg, #84fab0 0%, #8fd3f4 100%);
            -webkit-background-clip: text;
            -webkit-text-fill-color: transparent;
            font-weight: 800;
            font-size: 1.5rem;
        }

        /* 按钮样式 */
        .btn-logout {
            background: #ff6b6b;
            border: none;
            color: white;
            padding: 10px 30px;
            border-radius: 50px;
            font-weight: 600;
            transition: all 0.3s;
            margin-top: 10px;
            text-decoration: none;
            display: inline-block;
        }
        .btn-logout:hover {
            background: #ee5a52;
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(255, 107, 107, 0.4);
            color: white;
        }

        /* 动画定义 */
        @keyframes fadeIn {
            from { opacity: 0; transform: translateY(20px); }
            to { opacity: 1; transform: translateY(0); }
        }
    </style>
</head>
<body>

    <div class="welcome-card">
        <!-- 用户图标 -->
        <div class="avatar-icon">
            <i class="bi bi-person-circle"></i>
        </div>

        <!-- 欢迎文字 -->
        <h2 class="mb-3">登录成功!</h2>
        
        <p class="lead mb-4">
            欢迎回来,<br>
            <span class="user-highlight"><%= username %></span>
        </p>

        <hr class="my-4">

        <!-- 退出按钮 -->
        <!-- 注意:这里假设你有一个 logout.jsp 或者 logout Servlet -->
        <a href="logout.jsp" class="btn-logout">
            <i class="bi bi-box-arrow-right me-2"></i> 安全退出
        </a>
    </div>

</body>
</html>

5.logout.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
    // 1. 获取名为 "user" 的 Cookie
    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
        for (Cookie cookie : cookies) {
            if ("user".equals(cookie.getName())) {
                // 2. 设置有效期为 0,立即删除该 Cookie
                cookie.setMaxAge(0);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }
    }

    // 3. 重定向回登录页
    response.sendRedirect("login.jsp");
%>

五、调试过程与效果分析

在实验过程中,针对 Session 机制和 Cookie 机制进行了多轮调试,具体过程如下:

1.首次输入测试(注册逻辑)

操作:在登录页输入用户名“test”,密码“123456”,点击登录。

现象:页面未跳转,但在上方提示“注册成功!请在下方再次输入密码以完成登录。”。

分析:此时服务器端 Session 中已存入 registeredPassword = "123456",等待第二次验证。

2.密码不一致测试(验证逻辑)

操作:在第二次输入框中,故意输入错误密码“654321”。

现象:页面提示红色错误信息“密码与注册时不一致,登录失败。”。

分析:代码成功读取了 Session 中的旧密码,并与新输入进行了比对,逻辑正确。

3.登录成功与 Cookie 生成测试

操作:重新输入正确密码“123456”。

现象:页面成功跳转至 welcome.jsp,显示“登录成功!欢迎回来,test ”。

4.跨账号切换调试

问题:在 Session 未过期时,若直接切换新账号(如“nine”),系统仍要求输入旧账号的密码。

修复:在代码中增加了用户名比对逻辑。如果当前输入的用户名与 Session 中保存的用户名不一致,强制 session.invalidate()。

结果:修复后,切换账号时系统会自动重置状态,允许新账号重新进行“注册-登录”流程。

实验小

通过本次小组实验,我们深入理解了 Java Web 开发中 Session 与 Cookie 的协同工作机制,主要心得如下:

1.Session 与 Cookie 的分工:Session 适合用于短期、连续的交互状态保持。本实验中利用 Session 在两次表单提交之间“记住”密码,实现了类似“二次确认”的安全机制。Cookie 适合用于长期、跨会话的状态保持。通过设置 Max-Age,实现了“3天内免登录”的功能,减轻了服务器的存储压力。

2.状态管理的陷阱与解决:实验中遇到的最大挑战是“旧 Session 干扰新请求”的问题。由于服务器端默认复用 Session,导致更换用户时出现逻辑错误。通过增加“用户名一致性校验”并在不一致时销毁 Session,解决了这一并发场景下的逻辑漏洞。这让我认识到在处理用户状态时,必须考虑到用户行为的多样性和不确定性。

3.安全性思考:虽然本实验实现了自动登录,但在实际生产中,直接在 Cookie 中明文(或简单编码)存储密码存在安全风险。更安全的做法是存储一个加密的 Token(如 JWT),服务器端仅验证 Token 的有效性而不直接暴露用户凭证。

综上所述,本次实验不仅实现了预期的登录功能,更让我们掌握了 Web 应用中用户身份识别的核心技术细节。    

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐