杰瑞科技汇

java 登录 session

目录

  1. 核心概念
    • 什么是 Session?
    • Session 与 Cookie 的关系
    • 为什么登录需要 Session?
  2. 实现步骤详解
    • 第一步:创建登录页面
    • 第二步:创建登录处理 Servlet
    • 第三步:验证用户并创建 Session
    • 第四步:在需要登录的页面检查 Session
    • 第五步:实现登出功能
  3. 完整代码示例 (Servlet & JSP)
  4. 现代框架中的实现 (Spring Boot)
  5. 最佳实践与安全注意事项
  6. 常见问题与解决方案

核心概念

什么是 Session?

在 Web 应用中,Session(会话) 是一种服务器端的技术,用于跟踪同一个用户在多个请求之间的状态,当用户第一次访问网站时,服务器会为其创建一个独一无二的 Session 对象,并将其 ID(一个唯一的字符串)通过 Cookie 发送给用户的浏览器,之后,浏览器每次向服务器发送请求时,都会自动携带这个 Session ID,服务器通过 ID 找到对应的 Session 对象,从而“认出”这个用户。

java 登录 session-图1
(图片来源网络,侵删)

Session 与 Cookie 的关系

  • Session (会话):数据存储在服务器端,它相对安全,因为敏感信息不会直接暴露给客户端,但服务器需要消耗内存来存储这些会话数据。
  • Cookie (小甜饼):数据存储在客户端(浏览器),它通常只存储一个轻量级的文本数据,Session ID。

它们如何协作?

  1. 服务器创建 HttpSession 对象,并生成一个唯一的 ID,12345
  2. 服务器将 ID=12345 这条信息以 Cookie 的形式(JSESSIONID=12345)发送给浏览器。
  3. 浏览器将这个 Cookie 保存在本地。
  4. 在接下来的请求中,浏览器会自动带上这个 JSESSIONID Cookie。
  5. 服务器接收到请求后,从 Cookie 中提取 JSESSIONID,然后根据这个 ID 在服务器内存中找到对应的 HttpSession 对象。

为什么登录需要 Session?

登录的本质是验证用户身份,一旦验证成功,我们希望用户在访问网站的“受保护”页面(如个人中心、后台管理)时,服务器能够记住他已经登录了,而不用在每一个页面都重新输入用户名和密码。

Session 就是实现这个“记忆”功能的最佳工具,我们将用户的登录信息(如用户ID、用户名)存储在 Session 对象中,当用户访问受保护页面时,我们只需检查 Session 中是否存在这些信息即可,如果存在,说明用户已登录;如果不存在,则重定向到登录页面。


实现步骤详解 (传统 Servlet/JSP 方式)

假设我们使用一个简单的 User 类来表示用户。

java 登录 session-图2
(图片来源网络,侵删)

第一步:创建登录页面 (login.jsp)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>登录</title>
</head>
<body>
    <h2>用户登录</h2>
    <%-- 显示错误信息(如果存在) --%>
    <c:if test="${not empty errorMessage}">
        <p style="color: red;">${errorMessage}</p>
    </c:if>
    <form action="login" method="post">
        用户名: <input type="text" name="username"><br>
        密码: <input type="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>
  • action="login" 指定了表单提交的地址,对应一个名为 login 的 Servlet。
  • method="post" 表示使用 POST 方法提交数据。

第二步:创建登录处理 Servlet (LoginServlet.java)

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;
@WebServlet("/login") // 映射 Servlet 的 URL
public class LoginServlet extends HttpServlet {
    // 模拟一个用户数据库
    private static final String VALID_USERNAME = "admin";
    private static final String VALID_PASSWORD = "password123";
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 1. 验证用户名和密码
        if (VALID_USERNAME.equals(username) && VALID_PASSWORD.equals(password)) {
            // 2. 登录成功,获取 Session 对象
            // 如果请求中已有 Session,则返回该 Session;否则创建一个新的。
            HttpSession session = request.getSession();
            // 3. 将用户信息存入 Session
            // setAttribute(key, value)
            session.setAttribute("username", username);
            session.setAttribute("isLoggedIn", true);
            // 4. 重定向到主页(或其他受保护页面)
            // 使用重定向可以防止刷新页面时表单重复提交
            response.sendRedirect("welcome.jsp");
        } else {
            // 5. 登录失败,将错误信息存入 request 作用域
            request.setAttribute("errorMessage", "用户名或密码错误!");
            // 6. 转发回登录页面,并显示错误信息
            // 使用转发可以共享 request 中的数据
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    }
}

第三步:创建需要登录的页面 (welcome.jsp)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="javax.servlet.http.HttpSession" %>
<html>
<head>欢迎页面</title>
</head>
<body>
    <%
        // 1. 获取当前请求的 Session
        HttpSession session = request.getSession(false); // false 表示如果不存在不创建新 Session
        // 2. 检查 Session 中是否存在登录标记
        if (session == null || session.getAttribute("isLoggedIn") == null) {
            // Session 不存在或未登录,重定向到登录页
            response.sendRedirect("login.jsp");
            return; // 阻止后续代码执行
        }
    %>
    <h2>欢迎, ${sessionScope.username}!</h2>
    <p>您已成功登录。</p>
    <a href="logout">退出登录</a>
</body>
</html>
  • request.getSession(false) 是一个很好的实践,它只获取已存在的 Session,如果不存在则返回 null,避免了不必要的 Session 创建。
  • sessionScope.username 是 JSTL 的写法,等同于 ${session.getAttribute("username")}

第四步:实现登出功能 (LogoutServlet.java)

登出的核心是销毁 Session

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;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取 Session
        HttpSession session = request.getSession(false);
        // 2. Session 存在,则销毁它
        if (session != null) {
            session.invalidate(); // 使整个 Session 失效
        }
        // 3. 重定向到登录页面
        response.sendRedirect("login.jsp");
    }
}
  • session.invalidate() 会立即清除所有与该 Session 相关的数据,并使其失效。

完整代码示例 (Servlet & JSP)

项目结构:

webapp/
├── WEB-INF/
│   ├── web.xml (可选,如果用注解则不需要)
│   └── lib/
│       └── jstl-1.2.jar (JSP 标准标签库,用于在 JSP 中使用 EL 表达式)
├── login.jsp
├── welcome.jsp
└── WEB-INF/
    └── classes/
        └── com/
            └── example/
                └── servlet/
                    ├── LoginServlet.class
                    └── LogoutServlet.class

注意:login.jsp 顶部需要引入 JSTL 标签库: <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>


现代框架中的实现 (Spring Boot)

在现代框架如 Spring Boot 中,处理 Session 的方式更加简洁和强大,框架底层依然使用 Servlet API,但提供了更高级的抽象。

java 登录 session-图3
(图片来源网络,侵删)

配置 Session

application.propertiesapplication.yml 中配置 Session 超时时间(单位:秒)。

# 设置 Session 超时时间为 30 分钟
server.servlet.session.timeout=1800

登录 Controller

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpSession;
@Controller
public class LoginController {
    @GetMapping("/login")
    public String loginPage() {
        return "login"; // 返回 login.html 或 login.jsp 模板
    }
    @PostMapping("/login")
    public String login(@RequestParam String username,
                        @RequestParam String password,
                        HttpSession session,
                        RedirectAttributes redirectAttributes) {
        // 验证逻辑
        if ("admin".equals(username) && "password123".equals(password)) {
            // 将用户信息存入 Session
            session.setAttribute("username", username);
            session.setAttribute("isLoggedIn", true);
            return "redirect:/welcome"; // 重定向到欢迎页
        } else {
            // 添加重定向参数,用于显示错误信息
            redirectAttributes.addFlashAttribute("errorMessage", "用户名或密码错误!");
            return "redirect:/login";
        }
    }
    @GetMapping("/welcome")
    public String welcome(HttpSession session) {
        // 检查 Session
        if (session.getAttribute("isLoggedIn") == null) {
            return "redirect:/login";
        }
        return "welcome"; // 返回 welcome.html 模板
    }
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate(); // 销毁 Session
        return "redirect:/login";
    }
}

关键区别:

  • Spring Boot 使用 @Controller 注解来标记控制器。
  • HttpSession 作为方法参数直接注入,非常方便。
  • 使用 RedirectAttributes 来在重定向时传递数据,比 Servlet 的 request.setAttribute 更优雅。

最佳实践与安全注意事项

  1. 使用 HTTPS: 传输密码等敏感信息时,必须使用 HTTPS 协议,防止数据在传输过程中被窃听。
  2. 设置合理的 Session 超时时间: 默认的 Session 超时时间可能过长或过短,根据应用安全级别设置,15-30 分钟,用户长时间不活动后,应自动退出。
  3. 重要数据不要存入 Session: Session 存储在服务器内存中,如果用户量巨大,会消耗大量内存,在分布式/集群环境中,Session 需要特殊处理(如使用 Redis 共享 Session),只存储必要的、轻量的状态信息(如用户ID),而不要将整个用户对象或大量数据存入 Session。
  4. 防止 Session 固定攻击: 在用户登录成功后,最好调用 session.invalidate() 销毁旧的 Session,然后创建一个新的 Session,这样可以防止攻击者利用已知 Session ID 进行攻击。
    // 在登录成功后
    session.invalidate(); // 先销毁旧的
    HttpSession newSession = request.getSession(true); // 再创建一个新的
    newSession.setAttribute("username", username);
  5. 登出时一定要销毁 Session: 确保登出功能调用 session.invalidate(),否则用户关闭浏览器后,只要 Session 未超时,别人打开浏览器仍可能处于登录状态(取决于浏览器是否保存了 JSESSIONID Cookie)。
  6. CSRF 防护: 对于涉及状态改变的请求(如登录、登出、转账),应该实现 CSRF (Cross-Site Request Forgery) 防护,Spring Security 等框架都内置了 CSRF 防护机制。

常见问题与解决方案

问题 1: 为什么登录成功了,但跳转后页面又说未登录?

  • 原因 1: 浏览器禁用了 Cookie,Session 依赖 Cookie 来传递 ID,Cookie 被禁用,服务器无法识别用户。
  • 原因 2: 代码逻辑错误,在欢迎页面检查 Session 时,写成了 request.getSession(true),这会为未登录的用户也创建一个空的 Session,导致判断失误。应该使用 request.getSession(false)
  • 原因 3: 项目部署在负载均衡器或反向代理(如 Nginx)后面,配置不当导致 Session 共享失败,需要配置 sticky session 或使用外部存储(如 Redis)来共享 Session。

问题 2: 如何实现“记住我”功能?

“记住我”功能不能使用普通的 Session,因为 Session 有超时时间,通常的实现方式是:

  1. 用户勾选“记住我”并登录成功后,除了创建 Session,还在数据库中生成一个唯一的、长时效的 Token(有效期一个月)。
  2. 将这个 Token 存入数据库,并与用户 ID 关联。
  3. 将这个 Token 写入一个长期有效的 Cookie 中,发送给浏览器。
  4. 之后,用户每次访问时,首先检查这个长期 Cookie。
    • 如果存在,用 Token 去数据库查询。
    • 如果查询成功且未过期,就自动为用户登录(创建一个短期 Session)。
    • 如果查询失败或已过期,则视为未登录,重定向到登录页。

问题 3: 在分布式系统中如何处理 Session?

单个服务器的内存无法满足大规模用户的需求,且无法应对单点故障,解决方案是将 Session 数据集中存储

  • 使用 Redis: 这是最主流的方案,将 Session 数据存储在 Redis 中,所有应用服务器都连接同一个 Redis 实例,这样,无论用户的请求被哪个服务器处理,都能从 Redis 中获取到正确的 Session 数据。
  • 使用数据库: 将 Session 序列化后存入关系型数据库(如 MySQL),但性能通常不如 Redis。
  • 使用 Tomcat 集群: 配置 Tomcat 的集群模式,它会自动在各节点间复制 Session 数据,但这种方式对网络和性能有一定要求,不如 Redis 灵活和高效。
分享:
扫描分享到社交APP
上一篇
下一篇