session失效问题

场景描述

一个单体应用,启动两个服务,在同一个浏览器访问,登录上一个A服务后,再登录B服务,会导致A的session失效

分析

两个相同服务,彼此应该是没有影响的,但产生了影响,且不同浏览器就没有影响

源码

在一个登录过滤器中,有一段这样的代码,最终是由于session中没有userid导致表象的session失效,但实际上这个session已经不是之前携带userid的session了

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpSession session =  httpreq.getSession();
    String userid  = session.getAttribute("userid")==null?"":(String)session.getAttribute("userid");
    if(StringUtils.isEmpty(userid)){
        if(this.isAjaxRequest(httpreq)){
            httpres.getWriter().print("timeout");
            return;
        }
    }
}
 

关键在于以下代码

HttpSession session =  httpreq.getSession();

查看源码实现

RequestFacade:

@Override
public HttpSession getSession() {
​
    if (request == null) {
        throw new IllegalStateException(
            sm.getString("requestFacade.nullRequest"));
    }
​
    return getSession(true);
}
​
@Override
public HttpSession getSession(boolean create) {
​
    if (request == null) {
        throw new IllegalStateException(
            sm.getString("requestFacade.nullRequest"));
    }
​
    if (SecurityUtil.isPackageProtectionEnabled()){
        return AccessController.
            doPrivileged(new GetSessionPrivilegedAction(create));
    } else {
        return request.getSession(create);
    }
}

Request

@Override
public HttpSession getSession(boolean create) {
    Session session = doGetSession(create);
    if (session == null) {
        return null;
    }
​
    return session.getSession();
}
​
protected Session doGetSession(boolean create) {
​
    // There cannot be a session if no context has been assigned yet
    if (context == null) {
        return (null);
    }
​
    // Return the current session if it exists and is valid
    if ((session != null) && !session.isValid()) {
        session = null;
    }
    if (session != null) {
        return (session);
    }
​
    // Return the requested session if it exists and is valid
    Manager manager = null;
    if (context != null) {
        manager = context.getManager();
    }
    if (manager == null)
    {
        return (null);      // Sessions are not supported
    }
    if (requestedSessionId != null) {
        try {
            session = manager.findSession(requestedSessionId);
        } catch (IOException e) {
            session = null;
        }
        if ((session != null) && !session.isValid()) {
            session = null;
        }
        if (session != null) {
            session.access();
            return (session);
        }
    }
​
    // Create a new session if requested and the response is not committed
    if (!create) {
        return (null);
    }
    if ((context != null) && (response != null) &&
        context.getServletContext().getEffectiveSessionTrackingModes().
        contains(SessionTrackingMode.COOKIE) &&
        response.getResponse().isCommitted()) {
        throw new IllegalStateException
            (sm.getString("coyoteRequest.sessionCreateCommitted"));
    }
​
    // Attempt to reuse session id if one was submitted in a cookie
    // Do not reuse the session id if it is from a URL, to prevent possible
    // phishing attacks
    // Use the SSL session ID if one is present.
    if (("/".equals(context.getSessionCookiePath())
         && isRequestedSessionIdFromCookie()) || requestedSessionSSL ) {
        session = manager.createSession(getRequestedSessionId());
    } else {
        session = manager.createSession(null);
    }
​
    // Creating a new session cookie based on that session
    if ((session != null) && (getContext() != null)
        && getContext().getServletContext().
        getEffectiveSessionTrackingModes().contains(
            SessionTrackingMode.COOKIE)) {
        Cookie cookie =
            ApplicationSessionCookieConfig.createSessionCookie(
            context, session.getIdInternal(), isSecure());
​
        response.addSessionCookieInternal(cookie);
    }
​
    if (session == null) {
        return null;
    }
​
    session.access();
    return session;
}

以上有两段关键代码

// 1
if ((session != null) && !session.isValid()) {
    session = null;
}
// 2
// Creating a new session cookie based on that session
if ((session != null) && (getContext() != null)
    && getContext().getServletContext().
    getEffectiveSessionTrackingModes().contains(
        SessionTrackingMode.COOKIE)) {
    Cookie cookie =
        ApplicationSessionCookieConfig.createSessionCookie(
        context, session.getIdInternal(), isSecure());
​
    response.addSessionCookieInternal(cookie);
}

ApplicationSessionCookieConfig:

public static Cookie createSessionCookie(Context context,
            String sessionId, boolean secure) {
​
        SessionCookieConfig scc =
            context.getServletContext().getSessionCookieConfig();
​
        // NOTE: The priority order for session cookie configuration is:
        //       1. Context level configuration
        //       2. Values from SessionCookieConfig
        //       3. Defaults
        // 主要看这段,创建cookie,并将sessionId存储到cookie中
        Cookie cookie = new Cookie(
                SessionConfig.getSessionCookieName(context), sessionId);
  

SessionConfig:

/**
  * Determine the name to use for the session cookie for the provided
  * context.
  * @param context
 */
public static String getSessionCookieName(Context context) {
​
    String result = getConfiguredSessionCookieName(context);
​
    if (result == null) {
        result = DEFAULT_SESSION_COOKIE_NAME;
    }
​
    return result;
}
​
private static String getConfiguredSessionCookieName(Context context) {
​
    // Priority is:
    // 1. Cookie name defined in context
    // 2. Cookie name configured for app
    // 3. Default defined by spec
    if (context != null) {
        String cookieName = context.getSessionCookieName();
        if (cookieName != null && cookieName.length() > 0) {
            return cookieName;
        }
​
        SessionCookieConfig scc =
            context.getServletContext().getSessionCookieConfig();
        cookieName = scc.getName();
        if (cookieName != null && cookieName.length() > 0) {
            return cookieName;
        }
    }
​
    return null;
}

1: 验证session不为空且有效,根据结果(返回新的session)可知,之前的session已经失效,原因继续看

2: 1和2之前生成了新的session,此时的session是新产生的,将此sessionId存储到cookie中,也就是说获取session时应该会去cookie中找sessionId。

那么问题就很清晰了,相同的服务,开启两个,后一个(B服务)产生的session将sessionId存储至cookie中,导致A服务(前一个)的sessionId被覆盖(详见cookie),导致session失效,即1中session失效的原因,于是创建新的session,新的session中没有userid,导致A服务的表象的session失效(这里的表象是此应用的相关业务)。

cookie

根据SessionConfig的代码调试可知,本应用从context中获得到了cookieName为null,最终使用的是默认name:JSESSIONID,Cookie是面向路径的, 在一个目录页面里设置的 Cookie 在另一个目录的页面里是看不到的 ,但这两个服务都是在本地启动,域名都为localhost,所以会相互覆盖

 

<think> 我们正在解决Session失效问题。根据引用[1]和引用[2]的内容,我们可以总结出一些解决Session失效的方法。 引用[1]提到,当Session失效时,可以通过读取Cookies来恢复Session,即使用Cookies作为备用机制。 引用[2]则提到了跨域情况下Session失效问题,由于跨域请求默认不携带凭据(如cookies),导致Session无法维持。 因此,解决Session失效问题可以从以下几个方面入手: 1. **利用Cookies恢复Session**:在Session失效时,检查Cookies中是否存有相关信息,如果有,则重新设置Session。这种方法适用于非跨域的情况,且需要确保在用户会话期间Cookies有效。 2. **解决跨域问题**:如果是跨域导致Session失效,需要在服务器端设置允许跨域请求携带凭据(如cookies),同时客户端也需要设置withCredentials为true。 下面详细说明: ### 方法1:利用Cookies恢复Session(适用于非跨域) 步骤: - 在用户登录成功时,除了设置Session,还可以将一些必要信息(如用户ID、登录令牌等)存储在客户端的Cookies中(注意设置HttpOnly和Secure以增强安全性)。 - 每次请求时,检查Session是否存在,如果不存在(即Session失效),则读取Cookies中的信息,重新构建Session。 示例代码(服务器端,以Java为例): ```java public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse res = (HttpServletResponse) response; // 检查当前Session中是否有用户信息 HttpSession session = req.getSession(false); if (session == null || session.getAttribute("user") == null) { // 尝试从Cookies中获取用户信息 Cookie[] cookies = req.getCookies(); String userId = null; String token = null; if (cookies != null) { for (Cookie cookie : cookies) { if (cookie.getName().equals("userId")) { userId = cookie.getValue(); } else if (cookie.getName().equals("token")) { token = cookie.getValue(); } } } // 验证token的有效性(例如检查数据库或缓存) if (userId != null && token != null && validateToken(userId, token)) { // 重新创建Session并设置用户信息 session = req.getSession(true); session.setAttribute("user", userId); } } chain.doFilter(request, response); } ``` ### 方法2:解决跨域导致的Session失效 在跨域请求中,默认情况下浏览器不会发送Cookies。因此,需要: - 服务器端设置响应头允许跨域并允许携带凭据。 - 客户端在发送请求时设置`withCredentials`为true。 以Express为例: 服务器端设置: ```javascript const express = require('express'); const session = require('express-session'); const app = express(); app.use(session({ /* session配置 */ })); // 允许跨域并携带凭据 app.use(function(req, res, next) { res.header('Access-Control-Allow-Origin', 'http://client-domain.com'); // 不能为'*',必须指定具体域名 res.header('Access-Control-Allow-Credentials', true); res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); next(); }); ``` 客户端(使用fetch): ```javascript fetch('http://server-domain.com/api', { method: 'GET', credentials: 'include' // 携带凭据(cookies) }); ``` 或者使用XMLHttpRequest: ```javascript var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://server-domain.com/api', true); xhr.withCredentials = true; xhr.send(); ``` ### 其他常见方法 3. **延长Session过期时间**:通过设置更长的Session超时时间,减少失效的发生。 4. **使用Token机制(如JWT)**:完全避免Session,使用Token进行身份验证,这样就不存在Session失效问题,但需要注意Token的刷新机制。 5. **分布式Session管理**:在分布式环境中,使用集中式的Session存储(如Redis),避免因为服务器重启或扩展导致Session丢失。 根据实际场景选择合适的方法。如果是单应用且非跨域,方法1简单有效;如果是跨域应用,必须使用方法2;如果是分布式应用,则需要使用集中式Session存储。 最后,注意安全:在Cookies中存储敏感信息时,务必使用Secure标记(仅HTTPS)和HttpOnly(防止XSS攻击)。 以上是解决Session失效的常用方法。[^1][^2] §§相关问题§§ 1. 如何安全地在Cookies中存储用户信息? 2. 如何实现分布式Session管理? 3. JWT(JSON Web Token)的工作原理是什么? 4. 如何设置Session的过期时间? 5. 在跨域情况下,除了设置withCredentials,还有哪些注意事项? </think> ### 解决Session失效问题的综合方法 Session失效是Web开发中的常见问题,以下是五种核心解决方案及其实现逻辑: --- #### 1. **Cookie-Session协同机制** **原理**:用Cookie备份关键Session数据 **实现步骤**: ```java // 登录时设置双重存储 HttpSession session = request.getSession(); session.setAttribute("user", userData); // 同时存入Cookie(加密存储) Cookie backupCookie = new Cookie("session_backup", encrypt(userData)); backupCookie.setHttpOnly(true); backupCookie.setMaxAge(3600 * 24 * 7); // 7天 response.addCookie(backupCookie); // Session失效后恢复逻辑 if(session.getAttribute("user") == null) { Cookie[] cookies = request.getCookies(); for(Cookie cookie : cookies) { if("session_backup".equals(cookie.getName())) { UserData user = decrypt(cookie.getValue()); session.setAttribute("user", user); // 重建Session break; } } } ``` > **优势**:避免用户重复登录,体验平滑[^1] --- #### 2. **跨域Session同步方案** **场景**:前端端口(5500)访问后端端口(80) **解决方案**: ```javascript // 前端请求配置 (axios示例) axios.get('http://backend:80/api/data', { withCredentials: true // 关键:启用凭据传输 }) // 后端Express配置 const corsOptions = { origin: 'http://frontend:5500', credentials: true // 允许跨域凭据 } app.use(cors(corsOptions)); // Session中间件配置 app.use(session({ secret: 'your_secret', cookie: { sameSite: 'none', // 跨域必需 secure: true // HTTPS必需 } })) ``` > **要点**:SameSite=None + Secure + CORS凭据[^2] --- #### 3. **心跳检测与自动续期** **实现方案**: ```javascript // 前端定时发送心跳请求 setInterval(() => { fetch('/session/heartbeat', { method: 'POST' }) }, 300000); // 5分钟一次 // 后端更新Session过期时间 app.post('/session/heartbeat', (req, res) => { req.session.touch(); // 刷新过期时间 res.sendStatus(200); }) ``` --- #### 4. **分布式Session存储** **架构**: ```mermaid graph LR A[客户端] --> B[负载均衡] B --> C[服务节点1] B --> D[服务节点2] C & D --> E[(Redis集群)] ``` **配置要点**: ```java // Spring Boot配置示例 spring.session.store-type=redis spring.redis.host=redis-cluster server.servlet.session.timeout=1800s // 30分钟 ``` --- #### 5. **双Token无状态方案** **替代Session的方案**: ```javascript // 登录响应生成双Token { "access_token": "eyJhbG...", // 短期令牌(30min) "refresh_token": "eyJhbGc..." // 长期令牌(7天) } // 令牌刷新接口 app.post('/refresh', (req, res) => { const refreshToken = req.body.refresh_token; if(validate(refreshToken)) { const newAccessToken = generateToken(user, '30m'); res.json({ access_token: newAccessToken }); } }) ``` --- ### 最佳实践建议 1. **安全增强** - Cookie设置`HttpOnly`和`Secure`属性 - Session ID使用强随机数生成(熵值≥128位) 2. **失效监控** ```java // 拦截器记录Session事件 public class SessionListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent se) { log.info("Session创建: {}", se.getSession().getId()); } @Override public void sessionDestroyed(HttpSessionEvent se) { log.warn("Session失效: {}", se.getSession().getId()); } } ``` 3. **多端适配** ```http ### 移动端Token方案 Authorization: Bearer <access_token> ### Web端Cookie方案 Cookie: SESSIONID=<encrypted_data> ``` > **核心原则**:根据应用场景选择Session维护策略,高频交互系统推荐心跳+Redis方案,跨域应用推荐Token方案[^1][^2]
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

future_1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值