基于Java Servlet的用户登录系统设计与实现

本文将详细介绍一个基于Java Servlet的Web应用中的用户登录系统的设计与实现。该系统包含完整的JavaBean、DAO层、Servlet控制器和前端JSP页面。

1. 系统架构设计

1.1 三层架构

系统采用经典的三层架构:

  1. 表示层:JSP页面,负责用户界面展示

  2. 控制层:Servlet,负责请求处理和业务逻辑控制

  3. 数据访问层:DAO类,负责数据库操作

1.2 组件关系图

复制代码
┌─────────────┐     ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   JSP页面    │────>│   Servlet   │────>│    DAO层    │────>│   MySQL数据库│
│ (login.jsp) │     │ (LoginServlet)│     │ (UserDAO)   │     │             │
└─────────────┘     └─────────────┘     └─────────────┘     └─────────────┘
       ↑                    ↑                    ↑
       │                    │                    │
       └────────────────────┼────────────────────┘
               Session管理   │        实体类传递
                            │       (User.java)
                            │
                    ┌─────────────┐
                    │   JavaBean  │
                    │  (User类)   │
                    └─────────────┘

2. 核心组件详解

2.1 实体类:User.java

复制代码
package org.example;

import java.sql.Timestamp;

public class User {
    private int id;              // 用户ID,主键
    private String username;     // 用户名
    private String password;     // 密码
    private String email;        // 邮箱
    private Timestamp createdAt; // 创建时间

    // 无参构造函数(必须)
    public User() {
    }

    // 带参数的构造函数(用于快速创建对象)
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    // Getter和Setter方法
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Timestamp getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Timestamp createdAt) {
        this.createdAt = createdAt;
    }
}

设计说明

  • 遵循JavaBean规范,提供无参构造函数

  • 所有属性私有化,通过getter/setter访问

  • 包含基础的验证逻辑需要的字段

2.2 数据访问层:UserDAO.java

复制代码
package org.example;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class UserDAO {
    // 数据库连接配置
    private String jdbcURL = "jdbc:mysql://8.138.244.85:3306/shop?useSSL=false&serverTimezone=UTC";
    private String jdbcUsername = "admin";
    private String jdbcPassword = "sun123";

    // SQL语句常量定义
    private static final String SELECT_ALL_USERS = "SELECT id, username, password, email, created_at FROM users ORDER BY id DESC";
    private static final String SELECT_USER_BY_ID = "SELECT id, username, password, email, created_at FROM users WHERE id = ?";
    private static final String INSERT_USER = "INSERT INTO users (username, password, email) VALUES (?, ?, ?)";
    private static final String UPDATE_USER = "UPDATE users SET username = ?, password = ?, email = ? WHERE id = ?";
    private static final String DELETE_USER = "DELETE FROM users WHERE id = ?";
    private static final String SELECT_USER_BY_USERNAME = "SELECT id, username, password, email FROM users WHERE username = ?";

    // 数据库连接方法
    protected Connection getConnection() {
        Connection connection = null;
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            connection = DriverManager.getConnection(jdbcURL, jdbcUsername, jdbcPassword);
        } catch (SQLException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return connection;
    }

    // 用户验证方法(核心方法)
    public boolean validate(User user) {
        boolean status = false;
        try (Connection connection = getConnection();
             PreparedStatement preparedStatement = connection.prepareStatement(
                     "SELECT * FROM users WHERE username = ? AND password = ?")) {
            preparedStatement.setString(1, user.getUsername());
            preparedStatement.setString(2, user.getPassword());
            ResultSet rs = preparedStatement.executeQuery();
            status = rs.next(); // 如果查询到结果,返回true
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return status;
    }

    // 其他CRUD操作方法...
    // 省略了selectAllUsers、insertUser、updateUser等方法
}

安全考虑

  1. 使用PreparedStatement防止SQL注入

  2. 使用try-with-resources确保资源自动关闭

  3. 密码存储应考虑加密(实际项目中应使用加密存储)

2.3 控制器:LoginServlet.java

复制代码
package org.example;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private UserDAO userDAO;

    // 初始化方法
    public void init() {
        userDAO = new UserDAO();
    }

    // POST请求处理方法
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        // 1. 获取请求参数
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        // 2. 创建用户对象
        User user = new User();
        user.setUsername(username);
        user.setPassword(password);

        try {
            // 3. 验证用户凭据
            if (userDAO.validate(user)) {
                // 4. 创建会话并设置属性
                HttpSession session = request.getSession();
                session.setAttribute("loggedIn", "true");
                session.setAttribute("username", username);
                
                // 5. 登录成功,重定向到管理页面
                response.sendRedirect("admin.jsp");
            } else {
                // 6. 登录失败,重定向回登录页并传递错误参数
                response.sendRedirect("login.jsp?error=true");
            }
        } catch (Exception e) {
            e.printStackTrace();
            // 7. 异常处理,重定向到错误页面
            response.sendRedirect("error.jsp");
        }
    }
}

会话管理

  • 使用HttpSession存储用户登录状态

  • 防止会话固定攻击(实际项目应考虑session regeneration)

2.4 前端页面:login.jsp

复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户登录</title>
    <style>
        /* 整体布局样式 */
        body {
            font-family: Arial, sans-serif;
            background-color: #f5f5f5;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
        }
        
        /* 登录容器样式 */
        .login-container {
            background-color: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            width: 300px;
        }
        
        h2 {
            text-align: center;
            color: #333;
        }
        
        /* 表单组样式 */
        .form-group {
            margin-bottom: 15px;
        }
        
        label {
            display: block;
            margin-bottom: 5px;
            color: #555;
        }
        
        /* 输入框样式 */
        input[type="text"],
        input[type="password"] {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
        }
        
        /* 按钮样式 */
        button {
            width: 100%;
            padding: 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        
        button:hover {
            background-color: #45a049;
        }
        
        /* 错误信息样式 */
        .error {
            color: red;
            text-align: center;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>用户登录</h2>
        
        <!-- 登录表单 -->
        <form action="login" method="post">
            <div class="form-group">
                <label for="username">用户名:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">密码:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <button type="submit">登录</button>
        </form>
        
        <!-- 错误信息显示 -->
        <%
            String error = request.getParameter("error");
            if (error != null) {
        %>
            <div class="error">登录失败,请检查用户名和密码</div>
        <% } %>
    </div>
</body>
</html>

前端特点

  1. 响应式设计,居中布局

  2. 表单验证使用HTML5的required属性

  3. 通过JSP脚本动态显示错误信息

  4. 简洁美观的CSS样式

3. 配置文件

3.1 web.xml配置

复制代码
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    
    <!-- 配置登录Servlet -->
    <servlet>
        <servlet-name>LoginServlet</servlet-name>
        <servlet-class>org.example.LoginServlet</servlet-class>
    </servlet>
    
    <!-- Servlet映射 -->
    <servlet-mapping>
        <servlet-name>LoginServlet</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
    
    <!-- 欢迎页面 -->
    <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
    </welcome-file-list>
    
    <!-- 会话超时配置 -->
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    
</web-app>

4. 数据库设计

4.1 用户表结构

复制代码
CREATE TABLE users (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    status TINYINT DEFAULT 1 COMMENT '1-正常, 0-禁用'
);

-- 创建索引
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_email ON users(email);

5. 安全考虑与改进建议

5.1 当前实现的安全问题

  1. 密码明文存储:数据库中存储明文密码

  2. 缺乏密码复杂度验证

  3. 没有防止暴力破解机制

  4. 缺少CSRF防护

5.2 改进建议

5.2.1 密码加密存储
复制代码
// 使用BCrypt加密密码
import org.mindrot.jbcrypt.BCrypt;

public class PasswordUtil {
    // 密码加密
    public static String hashPassword(String plainPassword) {
        return BCrypt.hashpw(plainPassword, BCrypt.gensalt(12));
    }
    
    // 密码验证
    public static boolean checkPassword(String plainPassword, String hashedPassword) {
        return BCrypt.checkpw(plainPassword, hashedPassword);
    }
}
5.2.2 增强的UserDAO验证方法
复制代码
public User validateWithEncryption(String username, String plainPassword) {
    User user = null;
    String sql = "SELECT id, username, password, email FROM users WHERE username = ? AND status = 1";
    
    try (Connection conn = getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        
        pstmt.setString(1, username);
        ResultSet rs = pstmt.executeQuery();
        
        if (rs.next()) {
            String storedHash = rs.getString("password");
            
            // 验证密码(使用BCrypt)
            if (PasswordUtil.checkPassword(plainPassword, storedHash)) {
                user = new User();
                user.setId(rs.getInt("id"));
                user.setUsername(rs.getString("username"));
                user.setEmail(rs.getString("email"));
            }
        }
    } catch (SQLException e) {
        e.printStackTrace();
    }
    return user;
}
5.2.3 添加登录限制机制
复制代码
public class LoginLimiter {
    private static final Map<String, LoginAttempt> attempts = new ConcurrentHashMap<>();
    private static final int MAX_ATTEMPTS = 5;
    private static final long LOCK_TIME = 15 * 60 * 1000; // 15分钟
    
    public static boolean isLocked(String username) {
        LoginAttempt attempt = attempts.get(username);
        if (attempt == null) return false;
        
        if (attempt.isLocked() && 
            (System.currentTimeMillis() - attempt.getLockTime()) < LOCK_TIME) {
            return true;
        }
        
        if (attempt.isLocked() && 
            (System.currentTimeMillis() - attempt.getLockTime()) >= LOCK_TIME) {
            attempts.remove(username); // 解锁
            return false;
        }
        
        return false;
    }
    
    public static void recordFailedAttempt(String username) {
        LoginAttempt attempt = attempts.getOrDefault(username, new LoginAttempt());
        attempt.increment();
        
        if (attempt.getCount() >= MAX_ATTEMPTS) {
            attempt.lock();
        }
        
        attempts.put(username, attempt);
    }
    
    public static void clearAttempts(String username) {
        attempts.remove(username);
    }
}

6. 扩展功能建议

6.1 添加记住我功能

复制代码
// 在LoginServlet中添加Cookie处理
if ("on".equals(request.getParameter("remember"))) {
    Cookie usernameCookie = new Cookie("rememberedUser", username);
    usernameCookie.setMaxAge(30 * 24 * 60 * 60); // 30天
    usernameCookie.setHttpOnly(true);
    response.addCookie(usernameCookie);
}

6.2 添加验证码功能

复制代码
// 生成验证码
public class CaptchaUtil {
    public static String generateCaptcha() {
        // 生成4位随机数字
        Random random = new Random();
        return String.format("%04d", random.nextInt(10000));
    }
    
    public static BufferedImage generateCaptchaImage(String captchaText) {
        // 创建验证码图片
        // 实现图片生成逻辑
        return null;
    }
}

7. 总结

本文详细介绍了基于Java Servlet的用户登录系统的设计与实现。系统采用经典的三层架构,具有良好的扩展性和维护性。虽然当前实现是一个基础版本,但提供了完整的工作流程和清晰的代码结构。

核心优势

  1. 清晰的架构分层

  2. 完整的错误处理机制

  3. 良好的用户体验设计

  4. 易于扩展和维护

需要改进的地方

  1. 密码安全性需要加强

  2. 添加更多的安全防护机制

  3. 考虑分布式部署时的会话管理

此系统可以作为学习Java Web开发的良好起点,也可以作为实际项目的基础框架进行扩展。

相关推荐
散一世繁华,颠半世琉璃2 小时前
从 0 到 1 优化 Java 系统:方法论 + 工具 + 案例全解析
java·性能优化·操作系统
JasmineWr2 小时前
Java SPI和OSGi
java·开发语言
Lisonseekpan2 小时前
@Autowired 与 @Resource区别解析
java·开发语言·后端
Gu_yyqx2 小时前
Maven管理工具
java·maven
悦悦子a啊2 小时前
Maven 项目实战入门之--学生管理系统
java·数据库·oracle
晨光32112 小时前
Day34 模块与包的导入
java·前端·python
知行合一。。。2 小时前
Python--01--核心基础
android·java·python
计算机毕设指导62 小时前
基于微信小程序的水上警务通系统【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
陌生的人儿2 小时前
老年痴呆患者心血管防护,硝酸甘油使用需 “专人监护”
java·eclipse·tomcat·maven·0.3mg硝酸甘油舌下片