基于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开发的良好起点,也可以作为实际项目的基础框架进行扩展。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值