杰瑞科技汇

Java session登录如何实现与安全管理?

核心概念:Session 是什么?

在用户登录的场景中,Session 是一种服务器端的机制,用于在多个 HTTP 请求之间保持用户的登录状态。

Java session登录如何实现与安全管理?-图1
(图片来源网络,侵删)
  • 问题背景:HTTP 协议本身是无状态的,这意味着服务器无法区分两个来自同一个浏览器的请求,你第一次请求登录页面,服务器响应给你登录表单;你提交登录信息,服务器验证通过,但下一次你请求用户主页时,服务器已经不记得“刚刚验证通过的你”是谁了。
  • Session 的解决方案
    1. 用户成功登录后,服务器会创建一个唯一的、随机的字符串,称为 Session ID
    2. 服务器将这个 Session ID 与当前登录用户的信息(如用户名、ID、角色等)一起存储在服务器内存(或数据库、Redis等)中。
    3. 服务器通过一个名为 JSESSIONID 的 Cookie 将这个 Session ID 发送给用户的浏览器。
    4. 浏览器在后续的每一次请求中,都会自动携带这个 JSESSIONID Cookie。
    5. 服务器收到请求后,通过读取 JSESSIONID,就能在服务器端找到对应的用户信息,从而“认出”这个用户,保持其登录状态。

一个形象的比喻

  • Session ID:就像一张“会员卡”的卡号。
  • 服务器端的 Session 存储:就像这家会员制商店的会员档案库。
  • 浏览器:就像顾客。
  • 登录过程:顾客第一次来,店员(服务器)核实身份后,发给他一张唯一的会员卡(Session ID),顾客下次再来,只要出示这张卡,店员就能在档案库里找到他的信息,为他提供服务,而无需每次都重新验证身份。

完整实现步骤(以 Servlet + JSP 为例)

这是一个经典的、易于理解的实现方式。

第 1 步:准备项目环境

确保你的项目(Maven 项目)包含了 Servlet API 和 JSP API 的依赖。

Maven pom.xml 示例:

<dependencies>
    <!-- Servlet API -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>4.0.1</version>
        <scope>provided</scope>
    </dependency>
    <!-- JSP API -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>javax.servlet.jsp-api</artifactId>
        <version>2.3.3</version>
        <scope>provided</scope>
    </dependency>
    <!-- JSTL (可选,但推荐用于在JSP中写Java代码) -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
</dependencies>

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

这是一个简单的表单,用于收集用户名和密码。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">登录页面</title>
</head>
<body>
    <h2>用户登录</h2>
    <%-- 显示错误信息 --%>
    <% if (request.getAttribute("errorMsg") != null) { %>
        <p style="color:red;">${errorMsg}</p>
    <% } %>
    <form action="login" method="post">
        用户名: <input type="text" name="username"><br>
        密码:   <input type="password" name="password"><br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

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

这是登录逻辑的核心,它接收表单数据,进行验证,然后决定是创建 Session 还是重定向回登录页。

import java.io.IOException;
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;
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    // 模拟一个用户数据库
    private static final String VALID_USERNAME = "admin";
    private static final String VALID_PASSWORD = "password123";
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取表单提交的用户名和密码
        String username = request.getParameter("username");
        String password = request.getParameter("password");
        // 2. 验证用户名和密码
        if (VALID_USERNAME.equals(username) && VALID_PASSWORD.equals(password)) {
            // 3. 验证成功,获取或创建一个 Session
            HttpSession session = request.getSession();
            // 4. 将用户信息存入 Session
            // 这是关键步骤!将用户标识存储在 session 中
            session.setAttribute("user", username); 
            session.setAttribute("userId", 1L); // 也可以存用户ID等其他信息
            // 5. 重定向到主页
            response.sendRedirect("welcome.jsp");
        } else {
            // 6. 验证失败,设置错误信息并转发回登录页
            request.setAttribute("errorMsg", "用户名或密码错误!");
            request.getRequestDispatcher("login.jsp").forward(request, response);
        }
    }
}

代码解释

  • request.getSession():这是获取 Session 的核心方法,如果请求中已有有效的 JSESSIONID,则返回对应的 Session 对象,如果没有,则创建一个新的 Session 对象。
  • session.setAttribute("user", username):将登录成功的用户名存入 Session,之后在应用的任何地方,只要能拿到 HttpSession 对象,就能通过 getAttribute("user") 获取到这个用户名。
  • response.sendRedirect("welcome.jsp"):使用重定向,这会让浏览器重新请求 welcome.jsp,并且浏览器地址栏的 URL 也会改变,这是登录成功后的标准做法。
  • request.getRequestDispatcher(...).forward(...):使用请求转发,这是登录失败后的做法,它不会改变浏览器地址栏的 URL,只是将请求在服务器内部传递给另一个资源(如 login.jsp)。

第 4 步:创建需要登录才能访问的页面 (welcome.jsp)

这个页面会检查 Session 中是否存在用户信息,如果不存在,则说明用户未登录,应被重定向到登录页。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">欢迎页面</title>
</head>
<body>
    <h2>欢迎您!</h2>
    <%-- 检查 session 中是否有 user 属性 --%>
    <c:if test="${empty sessionScope.user}">
        <%-- 如果没有,说明用户未登录,重定向到登录页 --%>
        <c:redirect url="login.jsp" />
    </c:if>
    <%-- 如果有,显示欢迎信息 --%>
    <p>欢迎, ${sessionScope.user}!</p>
    <p>您的用户ID是: ${sessionScope.userId}</p>
    <a href="logout">退出登录</a>
</body>
</html>

第 5 步:创建登出处理 Servlet (LogoutServlet.java)

登出的核心就是销毁 Session

import java.io.IOException;
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;
@WebServlet("/logout")
public class LogoutServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取当前 Session
        HttpSession session = request.getSession(false); // false 表示如果不存在 session,则不创建
        // 2. session 存在,则销毁它
        if (session != null) {
            session.invalidate(); // 销毁整个 session,所有绑定在 session 上的数据都会被清除
        }
        // 3. 重定向到登录页
        response.sendRedirect("login.jsp");
    }
}

代码解释

  • request.getSession(false):以“不创建”模式获取 Session,这通常用于登出或检查登录状态,因为我们不想在用户未登录时创建一个无用的 Session。
  • session.invalidate():这是销毁 Session 的方法,调用此方法后,服务器会立即删除该 Session 对象及其所有数据,并使对应的 JSESSIONID 失效。

最佳实践和注意事项

  1. 不要在 Session 中存放大对象:Session 存储在服务器内存中,如果存放大对象(如 List<BigObject>),会占用大量内存,影响服务器性能,甚至导致内存溢出,Session 应只存必要的、轻量级的用户标识信息(如 userId, username, roles)。
  2. 设置 Session 超时:用户可能忘记登出,导致 Session 长期有效,有安全隐患,应该在 web.xml 中配置 Session 的超时时间。
    <session-config>
        <session-timeout>30</session-timeout> <!-- 单位是分钟,30分钟 -->
    </session-config>
  3. 使用 HTTPS:Session ID 通过 Cookie 传输,如果使用 HTTP,这些数据是明文的,容易被窃取(Session 劫持),务必在生产环境中使用 HTTPS 来保护 Session ID 的安全。
  4. 防止 Session 固定攻击:在用户登录成功后,可以调用 session.invalidate() 创建一个全新的 Session,然后将用户信息存入这个新 Session,这样可以避免攻击者利用之前已知的 Session ID 来冒充用户。
    // 在 LoginServlet 中,验证成功后
    HttpSession oldSession = request.getSession(false);
    if (oldSession != null) {
        oldSession.invalidate(); // 销毁旧的(可能未登录的)session
    }
    HttpSession newSession = request.getSession(true); // 创建一个新的 session
    newSession.setAttribute("user", username);

现代框架中的 Session (如 Spring Boot)

在现代框架如 Spring Boot 中,底层原理完全相同,但使用方式更加简化和高级。

  • 自动配置:Spring Boot 自动配置了 HttpSession,你无需手动配置任何东西。
  • 注入 HttpSession:你可以在 Controller 的方法参数中直接注入 HttpSession 对象,框架会自动为你提供。
  • 注解驱动:Spring 提供了更强大的注解来处理会话和权限,如 @SessionAttributes@PreAuthorize

Spring Boot Controller 示例:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
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 视图
    }
    // 处理登录请求
    @PostMapping("/login")
    public String login(@RequestParam String username, 
                        @RequestParam String password, 
                        HttpSession session, 
                        RedirectAttributes redirectAttributes) {
        // 验证逻辑...
        if ("admin".equals(username) && "password123".equals(password)) {
            session.setAttribute("user", username);
            return "redirect:/welcome"; // 登录成功,重定向到欢迎页
        } else {
            // 使用 RedirectAttributes 将错误信息传递到重定向的页面
            redirectAttributes.addFlashAttribute("errorMsg", "用户名或密码错误!");
            return "redirect:/login"; // 登录失败,重定向回登录页
        }
    }
    // 需要登录才能访问的页面
    @GetMapping("/welcome")
    public String welcome(HttpSession session, Model model) {
        // 从 session 中获取用户信息
        String user = (String) session.getAttribute("user");
        if (user == null) {
            return "redirect:/login"; // 如果未登录,重定向到登录页
        }
        model.addAttribute("username", user);
        return "welcome"; // 返回 welcome.html 视图
    }
    // 处理登出
    @GetMapping("/logout")
    public String logout(HttpSession session) {
        session.invalidate(); // 销毁 session
        return "redirect:/login"; // 重定向到登录页
    }
}

Session 的替代方案:JWT (JSON Web Token)

随着前后端分离架构的流行,Session + Cookie 的模式不再是唯一选择,JWT 是目前非常流行的一种无状态认证方案。

  • 核心思想:不再将用户信息存储在服务器端的 Session 中,而是将用户信息(经过加密和签名)生成一个 Token,然后把这个 Token 返回给客户端,客户端在后续的每一次请求中,都通过 HTTP Header(如 Authorization: Bearer <token>)将 Token 发送给服务器,服务器只需验证 Token 的合法性即可,无需查询 Session 存储。
  • 优点
    • 无状态:服务器不需要存储 Session,易于扩展,适合分布式和微服务架构。
    • 跨域友好:Token 可以轻松地在不同域名之间传递。
    • 移动端友好:不依赖 Cookie,更适合移动 App。

简单对比

特性 Session (服务器端) JWT (客户端)
状态 有状态 无状态
存储位置 服务器内存/数据库/Redis 客户端 (LocalStorage, 内存)
扩展性 较差,需要共享 Session 存储 极好,无状态,易于水平扩展
安全性 依赖 Cookie,易受 CSRF 攻击 无 Cookie,无 CSRF 风险,但需防止 Token 窃取
实现复杂度 相对简单 相对复杂,需要处理加密和签名

对于传统的 Java Web 应用(如基于 JSP/Servlet 的项目),Session + Cookie 是实现登录认证最直接、最经典的方式。

  • 登录:验证成功后,request.getSession() 获取 Session,并用 session.setAttribute() 存储用户信息。
  • 权限校验:在每个受保护的页面,检查 session.getAttribute("user") 是否存在。
  • 登出:调用 session.invalidate() 销毁 Session。

对于现代化的、前后端分离的应用,JWT 正在成为更主流的选择,因为它更符合 RESTful 架构的无状态特性。

理解 Session 的核心原理是掌握 Web 应用认证的基础,无论你使用的是传统的 Servlet 还是现代的 Spring Boot。

分享:
扫描分享到社交APP
上一篇
下一篇