用户登录是 Web 应用中最基本的功能之一。本章将通过一个完整的登录功能实现,深入讲解 Servlet 如何处理表单数据、会话管理和页面跳转。
登录功能 Servlet 实现
下面是一个完整的用户登录 Servlet 实现:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
public void init() throws ServletException {
// Servlet 初始化代码
System.out.println("LoginServlet 初始化");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 如果是GET请求,直接重定向到登录页面
response.sendRedirect("login.jsp");
}
@Override
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");
if ("admin".equals(username) && "123456".equals(password)) {
// 登录成功,设置session
HttpSession session = request.getSession();
session.setAttribute("username", username);
session.setAttribute("loginTime", new java.util.Date());
// 重定向到欢迎页面
response.sendRedirect("welcome.jsp");
} else {
// 登录失败,重定向回登录页面并携带错误参数
response.sendRedirect("login.jsp?error=true");
}
}
@Override
public void destroy() {
// Servlet 销毁代码
System.out.println("LoginServlet 销毁");
}
}
登录页面 JSP 实现
配合登录 Servlet 的 JSP 页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<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: white;
padding: 40px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
width: 300px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="text"], input[type="password"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
.btn-login {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-login:hover {
background-color: #0056b3;
}
.error-message {
color: #dc3545;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
padding: 10px;
border-radius: 4px;
margin-bottom: 20px;
display: none;
}
.show-error {
display: block;
}
</style>
</head>
<body>
<div class="login-container">
<h2 style="text-align: center; margin-bottom: 30px;">用户登录</h2>
<div id="errorMessage" class="error-message">
用户名或密码错误!
</div>
<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" class="btn-login">登录</button>
</form>
<div style="margin-top: 20px; text-align: center; font-size: 12px; color: #666;">
<p>测试账号:admin</p>
<p>测试密码:123456</p>
</div>
</div>
<script>
// 检查URL参数,显示错误消息
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('error') === 'true') {
document.getElementById('errorMessage').classList.add('show-error');
}
// 自动隐藏错误消息
setTimeout(function() {
const errorDiv = document.getElementById('errorMessage');
if (errorDiv) {
errorDiv.classList.remove('show-error');
}
}, 5000);
</script>
</body>
</html>
欢迎页面 JSP 实现
登录成功后的欢迎页面:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.text.SimpleDateFormat" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎页面</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f8f9fa;
margin: 0;
padding: 20px;
}
.welcome-container {
max-width: 800px;
margin: 0 auto;
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.user-info {
background-color: #e9f7ef;
border: 1px solid #d1ecf1;
padding: 20px;
border-radius: 4px;
margin-bottom: 20px;
}
.btn-logout {
background-color: #dc3545;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
text-decoration: none;
display: inline-block;
}
.btn-logout:hover {
background-color: #c82333;
}
.nav-links {
margin-top: 20px;
}
.nav-links a {
margin-right: 15px;
text-decoration: none;
color: #007bff;
}
</style>
</head>
<body>
<div class="welcome-container">
<h1>欢迎来到系统</h1>
<%
// 检查用户是否登录
String username = (String) session.getAttribute("username");
java.util.Date loginTime = (java.util.Date) session.getAttribute("loginTime");
if (username == null) {
// 用户未登录,重定向到登录页面
response.sendRedirect("login.jsp");
return;
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
%>
<div class="user-info">
<h2>欢迎,<%= username %>!</h2>
<p><strong>登录时间:</strong> <%= sdf.format(loginTime) %></p>
<p><strong>当前时间:</strong> <%= sdf.format(new java.util.Date()) %></p>
<p><strong>会话ID:</strong> <%= session.getId() %></p>
</div>
<div class="nav-links">
<h3>功能菜单</h3>
<ul>
<li><a href="profile.jsp">个人资料</a></li>
<li><a href="settings.jsp">系统设置</a></li>
<li><a href="reports.jsp">数据报表</a></li>
</ul>
</div>
<div style="margin-top: 30px;">
<a href="logout" class="btn-logout">退出登录</a>
</div>
</div>
</body>
</html>
退出登录 Servlet
处理用户退出登录的功能:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 获取当前会话,如果不存在则不创建新会话
HttpSession session = request.getSession(false);
if (session != null) {
// 记录退出日志
String username = (String) session.getAttribute("username");
System.out.println("用户 " + username + " 退出登录");
// 使会话失效
session.invalidate();
}
// 重定向到登录页面
response.sendRedirect("login.jsp");
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
登录过滤器
用于检查用户访问权限的过滤器:
import java.io.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebFilter({"/*"})
public class AuthFilter implements Filter {
// 不需要登录就可以访问的路径
private static final String[] ALLOWED_PATHS = {
"/login",
"/login.jsp",
"/logout",
"/css/",
"/js/",
"/images/"
};
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("AuthFilter 初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String path = httpRequest.getRequestURI().substring(
httpRequest.getContextPath().length());
// 检查当前路径是否在允许列表中
boolean allowed = false;
for (String allowedPath : ALLOWED_PATHS) {
if (path.startsWith(allowedPath)) {
allowed = true;
break;
}
}
// 如果路径不在允许列表中,检查用户是否已登录
if (!allowed) {
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute("username") == null) {
// 用户未登录,重定向到登录页面
httpResponse.sendRedirect(httpRequest.getContextPath() + "/login.jsp");
return;
}
}
// 继续处理请求
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("AuthFilter 销毁");
}
}
增强版登录 Servlet
包含更多功能的登录 Servlet:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.annotation.*;
import javax.servlet.http.*;
@WebServlet("/enhanced-login")
public class EnhancedLoginServlet extends HttpServlet {
// 模拟用户数据库
private static final Map<String, String> USER_DB = new HashMap<>();
private static final Map<String, Integer> LOGIN_ATTEMPTS = new HashMap<>();
@Override
public void init() throws ServletException {
// 初始化测试用户
USER_DB.put("admin", "123456");
USER_DB.put("user1", "password1");
USER_DB.put("user2", "password2");
System.out.println("EnhancedLoginServlet 初始化完成");
}
@Override
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");
String rememberMe = request.getParameter("rememberMe");
// 检查登录尝试次数
if (isAccountLocked(username)) {
response.sendRedirect("login.jsp?error=locked");
return;
}
// 验证用户凭据
if (authenticate(username, password)) {
// 登录成功
resetLoginAttempts(username);
HttpSession session = request.getSession();
session.setAttribute("username", username);
session.setAttribute("loginTime", new java.util.Date());
session.setAttribute("userAgent", request.getHeader("User-Agent"));
// 设置记住我功能
if ("on".equals(rememberMe)) {
Cookie userCookie = new Cookie("rememberedUser", username);
userCookie.setMaxAge(30 * 24 * 60 * 60); // 30天
response.addCookie(userCookie);
}
// 记录登录日志
logLogin(username, request.getRemoteAddr());
// 重定向到原始请求页面或欢迎页面
String redirectUrl = request.getParameter("redirect");
if (redirectUrl != null && !redirectUrl.isEmpty()) {
response.sendRedirect(redirectUrl);
} else {
response.sendRedirect("welcome.jsp");
}
} else {
// 登录失败
recordFailedAttempt(username);
String errorType = "invalid";
if (isAccountLocked(username)) {
errorType = "locked";
}
response.sendRedirect("login.jsp?error=" + errorType);
}
}
private boolean authenticate(String username, String password) {
if (username == null || password == null) {
return false;
}
String storedPassword = USER_DB.get(username);
return storedPassword != null && storedPassword.equals(password);
}
private void recordFailedAttempt(String username) {
if (username != null) {
int attempts = LOGIN_ATTEMPTS.getOrDefault(username, 0) + 1;
LOGIN_ATTEMPTS.put(username, attempts);
System.out.println("用户 " + username + " 登录失败,尝试次数: " + attempts);
}
}
private void resetLoginAttempts(String username) {
if (username != null) {
LOGIN_ATTEMPTS.remove(username);
}
}
private boolean isAccountLocked(String username) {
if (username == null) {
return false;
}
Integer attempts = LOGIN_ATTEMPTS.get(username);
return attempts != null && attempts >= 5; // 5次失败后锁定
}
private void logLogin(String username, String ipAddress) {
System.out.println(String.format("用户登录 - 用户名: %s, IP地址: %s, 时间: %s",
username, ipAddress, new java.util.Date()));
}
}
关键知识点总结
- 会话管理:使用 HttpSession 跟踪用户登录状态
- 表单处理:通过 request.getParameter() 获取表单数据
- 页面跳转:使用 response.sendRedirect() 进行重定向
- 权限控制:通过过滤器实现统一的登录检查
- 错误处理:通过 URL 参数传递错误信息
- 安全措施:登录尝试次数限制、会话超时控制
最佳实践
- 始终对用户输入进行验证和清理
- 使用 HTTPS 保护登录数据传输
- 设置合理的会话超时时间
- 记录登录日志用于安全审计
- 实现登录失败次数限制防止暴力破解
- 使用密码哈希存储,不要在代码中硬编码密码
- 考虑实现验证码功能增强安全性
注意:在实际生产环境中,应该使用数据库存储用户信息,并对密码进行加密存储。硬编码的用户名密码仅用于演示目的。
提示: 这是一个重要的概念,需要特别注意理解和掌握。
注意: 这是一个常见的错误点,请避免犯同样的错误。