杰瑞科技汇

Java session为何取不到?

下面我将从最常见的原因排查步骤,为你提供一个全面、系统的解决方案。

Java session为何取不到?-图1
(图片来源网络,侵删)

核心原因:Session 机制是如何工作的?

在解决问题之前,先要理解 Session 的工作原理,这有助于你快速定位问题。

  1. 客户端请求服务器:用户第一次访问网站时,服务器会创建一个 HttpSession 对象。
  2. 服务器分配 Session ID:服务器会生成一个唯一的 JSESSIONIDA1B2C3D4E5F6A1B2C3D4E5F6)。
  3. 服务器响应客户端:服务器通过 Set-Cookie 响应头,将 JSESSIONID 发送给浏览器,并告诉浏览器将其保存下来。
    Set-Cookie: JSESSIONID=A1B2C3D4E5F6A1B2C3D4E5F6; Path=/; HttpOnly
  4. 客户端后续请求:浏览器在后续的每一次请求中,都会自动携带这个 JSESSIONID Cookie。
    Cookie: JSESSIONID=A1B2C3D4E5F6A1B2C3D4E5F6
  5. 服务器识别 Session:服务器根据请求中的 JSESSIONID,找到对应的 HttpSession 对象,从而实现“会话”状态保持。

Session 取不到的根本原因可以归结为:服务器无法在后续请求中找到与当前请求关联的 HttpSession 对象。 这通常是因为 JSESSIONID 没有被正确传递。


最常见的原因及解决方案

请逐一检查以下几点,它们能解决 90% 以上的问题。

前端问题:Cookie 未被正确携带

这是最常见的原因,尤其是在前后端分离(SPA)应用中。

Java session为何取不到?-图2
(图片来源网络,侵删)
  • 现象

    • 第一次请求时,服务器创建了 Session 并返回了 JSESSIONID
    • 但后续的 AJAX 请求或页面跳转请求中,浏览器没有携带 JSESSIONID
  • 原因

    • 跨域问题:前端应用域名与后端 API 域名不同,前端是 http://www.myapp.com,后端是 http://api.myapp.com,默认情况下,跨域请求不会携带 Cookie。
    • 前端代码未配置:手动发起的 AJAX 请求没有设置 withCredentials
  • 解决方案

    a) 后端配置 CORS (跨域资源共享)

    Java session为何取不到?-图3
    (图片来源网络,侵删)

    如果你使用的是 Spring Boot,可以这样配置:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.cors.CorsConfiguration;
    import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
    import org.springframework.web.filter.CorsFilter;
    @Configuration
    public class CorsConfig {
        @Bean
        public CorsFilter corsFilter() {
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            CorsConfiguration config = new CorsConfiguration();
            // 允许所有域名进行跨域调用(生产环境请指定具体域名)
            config.addAllowedOriginPattern("*"); 
            // 允许所有头信息
            config.addAllowedHeader("*");
            // 允许所有方法 (GET, POST, PUT, DELETE, OPTIONS)
            config.addAllowedMethod("*");
            // **关键点**:允许携带认证信息 (Cookie, Authorization headers)
            config.setAllowCredentials(true);
            // 暴露哪些头部信息给前端
            config.addExposedHeader("Content-Type");
            config.addExposedHeader("X-Total-Count");
            source.registerCorsConfiguration("/**", config);
            return new CorsFilter(source);
        }
    }

    注意setAllowCredentials(true)addAllowedOriginPattern("*") 不能同时使用(在最新 Spring 版本中,addAllowedOriginaddAllowedOriginPattern 不能为 ),更安全的做法是指定具体的前端域名,

    config.addAllowedOrigin("http://www.myapp.com");

    b) 前端 AJAX 请求配置

    在发起 AJAX 请求时,必须设置 withCredentials: true

    // 使用 fetch API
    fetch('http://api.myapp.com/api/data', {
      method: 'GET',
      credentials: 'include' // 关键!
    });
    // 使用 axios
    axios.get('http://api.myapp.com/api/data', {
      withCredentials: true // 关键!
    });

后端问题:浏览器或服务器禁用了 Cookie

  • 现象

    • 即使是同域请求,服务器也从未收到 JSESSIONID
  • 原因

    • 浏览器设置:用户在浏览器中禁用了 Cookie。
    • 服务器配置:在 web.xml 或 Spring Boot 配置中,显式禁用了 URL 重写,而浏览器又禁用了 Cookie,导致 Session 无法工作。
  • 解决方案

    a) 检查浏览器设置 提示用户检查浏览器设置,确保 Cookie 未被禁用。

    b) 使用 URL 重写作为备选方案 当 Cookie 不可用时,可以将 JSESSIONID 直接附加在 URL 后面,大多数现代 Web 框架(如 Spring)会自动处理,但需要开启配置。

    在 Spring Boot 中,可以通过以下配置开启 URL 重写:

    # application.properties
    server.servlet.session.tracking-modes=cookie, url

    在 JSP 页面中,可以通过 <%= response.encodeURL("yourpage.jsp") %> 来自动生成带有 JSESSIONID 的 URL。

代码问题:获取 Session 的方式错误

  • 现象

    • 在同一个请求处理方法中,第一次获取 Session 是 null,或者获取后存入的数据,在下一个请求中取不到。
  • 原因

    • 直接获取 HttpSession:在 Servlet 中,应该使用 request.getSession()request.getSession(true)request.getSession(false) 只会获取已存在的 Session,如果不存在则返回 null,这通常是错误的用法。
    • Spring MVC 中错误注入:在 Controller 中,应该注入 HttpSession 或使用 @SessionAttribute,而不是自己 new 一个。
  • 解决方案

    a) 正确获取 Session (Servlet 方式)

    // 推荐:Session 不存在,会自动创建一个新的
    HttpSession session = request.getSession(); 
    // 不推荐:Session 不存在,会返回 null,导致后续 NPE
    // HttpSession session = request.getSession(false);
    // 存值
    session.setAttribute("user", "John Doe");
    // 取值
    String user = (String) session.getAttribute("user");

    b) 正确使用 Session (Spring MVC 方式)

    import javax.servlet.http.HttpSession;
    @Controller
    public class MyController {
        @GetMapping("/login")
        public String login(HttpSession session) {
            // Spring 会自动将 HttpSession 作为参数注入
            session.setAttribute("user", "John Doe");
            return "success";
        }
        @GetMapping("/profile")
        public String profile(HttpSession session) {
            String user = (String) session.getAttribute("user");
            if (user == null) {
                // Session 取不到
                return "redirect:/login";
            }
            return "profile";
        }
    }

服务器配置问题:Session 过期或未持久化

  • 现象

    用户一段时间不操作后,再次访问,Session 就丢失了。

  • 原因

    • Session 超时:Session 的默认存活时间(通常是 30 分钟)已过。
    • 服务器重启:Session 没有配置持久化(如存入 Redis 或数据库),服务器重启后,内存中的 Session 会全部丢失。
  • 解决方案

    a) 调整 Session 超时时间

    web.xml 中:

    <session-config>
        <session-timeout>60</session-timeout> <!-- 单位:分钟 -->
    </session-config>

    在 Spring Boot 中:

    # application.properties
    server.servlet.session.timeout=30m

    b) 使用外部 Session 存储器(推荐)

    为了解决服务器重启 Session 丢失和实现集群 Session 共享,强烈建议使用 Redis 来存储 Session。

    Spring Boot 集成 Redis Session 示例:

    1. 添加依赖:

      <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-data-redis</artifactId>
      </dependency>
      <dependency>
          <groupId>org.springframework.session</groupId>
          <artifactId>spring-session-data-redis</artifactId>
      </dependency>
    2. 配置 Redis 连接:

      # application.properties
      spring.redis.host=localhost
      spring.redis.port=6379
    3. 添加 @EnableRedisHttpSession 注解:

      import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
      @SpringBootApplication
      @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800) // 30分钟
      public class MyApplication {
          public static void main(String[] args) {
              SpringApplication.run(MyApplication.class, args);
          }
      }

      配置完成后,所有 Session 操作都会自动与 Redis 同步。


系统性的排查步骤

如果以上方法都不能解决问题,请按照以下步骤进行排查:

  1. 第一步:检查浏览器开发者工具

    • 打开 Chrome/Firefox 的开发者工具 (F12)。
    • 切换到 Network (网络) 标签页。
    • 第一次请求:检查响应头中是否有 Set-Cookie: JSESSIONID=...
    • 后续请求:检查请求头中是否有 Cookie: JSESSIONID=...
      • 如果第一次没有 Set-Cookie,说明后端创建 Session 失败(检查后端代码和配置)。
      • 如果第一次有,但后续没有,说明是前端跨域或 Cookie 携带问题
      • 如果前后都有,但后端就是取不到,说明是后端代码逻辑问题Session 过期
  2. 第二步:简化测试

    • 创建一个最简单的 Servlet 或 Controller,专门用来测试 Session。

      @WebServlet("/test-session")
      public class TestSessionServlet extends HttpServlet {
      protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
          HttpSession session = request.getSession();
          session.setAttribute("test", "Hello Session");
          response.getWriter().println("Session created and value set. Session ID: " + session.getId());
      }
      }
    • 访问这个测试接口,然后写另一个接口去取值,看是否能成功,这可以帮你隔离问题。

  3. 第三步:检查日志

    • 查看服务器的日志(如 Tomcat 的 catalina.out 或 Spring Boot 的控制台日志)。
    • 有时框架会打印出 Session 相关的错误或警告信息。
  4. 第四步:检查框架和服务器版本

    确保你使用的框架(Spring, Spring Boot)和服务器(Tomcat, Jetty)版本是兼容的,有时特定版本的组合会存在已知的 Bug。

  5. 第五步:检查代理或负载均衡器

    • 如果你的应用部署在 Nginx、Apache 或其他负载均衡器后面,确保它们配置正确,并且没有在转发请求时意外地移除或修改 JSESSIONID Cookie,通常需要配置 proxy_cookie_pathsticky sessions (会话亲和性)。

问题现象 最可能的原因 解决方案
跨域请求 Session 丢失 前端未携带 JSESSIONID Cookie 后端配置 CORS 并设置 allowCredentials=true,前端 AJAX 设置 withCredentials=true
同域请求 Session 丢失 浏览器禁用 Cookie 提醒用户或配置 URL 重写
代码中 Session 为 null 错误使用了 getSession(false) 改为使用 getSession() 或检查注入方式
一段时间后 Session 丢失 Session 超时或服务器重启 调整超时时间或配置 Redis/Memcached 等外部存储
所有方法都试过还是不行 代理/负载均衡器配置问题 检查 Nginx 等代理的配置,确保 Cookie 正确传递

希望这份详细的指南能帮助你快速定位并解决 Session 取不到的问题!

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