Spring Session产生的sessionid与cookies中的sessionid不一样的问题 && httpOnly 设置不起作用

本文探讨了在Spring Boot 2.0环境下,使用Spring Session和Redis作为会话存储时遇到的问题,即从Cookies中获取的sessionID与通过sessionRepository获取的sessionID不一致。文章详细介绍了问题的原因在于Cookie中的sessionID进行了Base64编码,而默认配置下未进行解码,导致获取的session为null。提供了解决方案,包括自定义CookieSerializer以匹配sessionID编码方式,以及手动解码Cookie中的sessionID。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

摘自 : https://www.cnblogs.com/imyjy/p/9187168.html
背景:
  Springboot 2.0 (spring-session-data-redis + spring-boot-starter-web)
需求:

通过cookies中取到的 sessionid 获取到 session

预期效果:

@Autowired

private SessionRepositry sessionRepositry;

Session session = sessionRespositry.findById(sessionId);

真实结果: 获取到的session是null, 然而通过 request.getSession(); 可以获取到session, 说明 session是存在的.

问题追踪后发现问题:

cookie中的sessionId 与 session.getId() 不一样!!!

DEBUG:
  1. 先看一看SpringSession是如何从Cookie中获取sessionid的! (相关类: org.springframework.session.web.http.DefaultCookieSerializer)
在这里插入图片描述

2. 再看一看 useBase64Encoding 的值是啥, 首先看默认值

在这里插入图片描述

3. 看看这些配置是在哪里被(赋值)确认的, 一路追踪到 org.springframework.session.config.annotation.web.http.SpringHttpSessionConfiguration 配置类中

在这里插入图片描述
 看看 createDefaultCookieSerializer() 是如何实现的

在这里插入图片描述

4. 从上面可以得出结论, 我们无法 通过配置文件 中 server.servlet.session.** 来配置 useBase64Encoding. 使 cookie中的 sessionid 与 session.getId() 保持一致

5. 期间发现的另一个问题: 虽然 sessionCookieConfig 有httpOnly相关配置, 但这里并未将配置设入 cookieSerializer 中, 导致配置文件中的 server.servlet.session.cookie.httpOnly = false 不起作用

解决方案:

第一种方案: 通过配置 自定义的 CookieSerializer 来指定配置信息(如果觉得麻烦请直接看第二种方案), 如下

a) 首先因为 SessionCookieConfig 接口中并没有定义 isUseBase64Encoding() 等接口, 导致缺少了部分配置, 所以我 自定义了一个 MySessionCookieConfig 接口继承了 SessionCookieConfig, 并写了一个默认实现 MyDefaultSessionCookieConfig
 
  MySessionCookieConfig

package com.cardgame.demo.center.config;

import javax.servlet.SessionCookieConfig;

/**
 *
 * 补充 SessionCookie 中未定义的配置项
 * @author yjy
 * 2018-06-15 13:30
 */
public interface MySessionCookieConfig extends SessionCookieConfig {

    String getDomainPattern();

    void setDomainPattern(String domainPattern);

    String getJvmRoute();

    void setJvmRoute(String jvmRoute);

    boolean isUseBase64Encoding();

    void setUseBase64Encoding(boolean useBase64Encoding);

}

MyDefaultSessionCookieConfig

package com.cardgame.demo.center.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 *
 * 涵盖 CookieSerializer 所有配置项
 * @author yjy
 * 2018-06-15 13:31
 */
@Component
@ConfigurationProperties(prefix = "server.servlet.session.cookie")
public class MyDefaultSessionCookieConfig implements MySessionCookieConfig {

    private String name = "SESSION";
    private String path;
    private String domain;
    private String comment;
    private int maxAge = -1;
    private String domainPattern;
    private String jvmRoute;
    private boolean httpOnly = true;
    private boolean secure = false;
    private boolean useBase64Encoding = false;

    @Override
    public String getDomainPattern() {
        return domainPattern;
    }

    @Override
    public void setDomainPattern(String domainPattern) {
        this.domainPattern = domainPattern;
    }

    @Override
    public String getJvmRoute() {
        return jvmRoute;
    }

    @Override
    public void setJvmRoute(String jvmRoute) {
        this.jvmRoute = jvmRoute;
    }

    @Override
    public boolean isUseBase64Encoding() {
        return useBase64Encoding;
    }

    @Override
    public void setUseBase64Encoding(boolean useBase64Encoding) {
        this.useBase64Encoding = useBase64Encoding;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String getPath() {
        return path;
    }

    @Override
    public void setPath(String path) {
        this.path = path;
    }

    @Override
    public String getDomain() {
        return domain;
    }

    @Override
    public void setDomain(String domain) {
        this.domain = domain;
    }

    @Override
    public String getComment() {
        return comment;
    }

    @Override
    public void setComment(String comment) {
        this.comment = comment;
    }

    @Override
    public boolean isHttpOnly() {
        return httpOnly;
    }

    @Override
    public void setHttpOnly(boolean httpOnly) {
        this.httpOnly = httpOnly;
    }

    @Override
    public boolean isSecure() {
        return secure;
    }

    @Override
    public void setSecure(boolean secure) {
        this.secure = secure;
    }

    @Override
    public int getMaxAge() {
        return maxAge;
    }

    @Override
    public void setMaxAge(int maxAge) {
        this.maxAge = maxAge;
    }
}

b) 利用 MyDefaultSessionCookieConfig 携带的配置, 自定义 CookieSerializer Bean

package com.cardgame.demo.center.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
import org.springframework.session.security.web.authentication.SpringSessionRememberMeServices;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;

import javax.servlet.ServletContext;
import javax.servlet.SessionCookieConfig;

/**
 *
 * @author yjy
 * 2018-06-08 14:53
 */
@Slf4j
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600, redisNamespace = "center")
public class RedisSessionConfig implements ApplicationContextAware {

    @Bean
    public CookieSerializer cookieSerializer(ServletContext servletContext, MySessionCookieConfig sessionCookieConfig) {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        if (servletContext != null) {
            if (sessionCookieConfig != null) {
                if (sessionCookieConfig.getName() != null)
                    cookieSerializer.setCookieName(sessionCookieConfig.getName());
                if (sessionCookieConfig.getDomain() != null)
                    cookieSerializer.setDomainName(sessionCookieConfig.getDomain());
                if (sessionCookieConfig.getPath() != null)
                    cookieSerializer.setCookiePath(sessionCookieConfig.getPath());
                if (sessionCookieConfig.getMaxAge() != -1)
                    cookieSerializer.setCookieMaxAge(sessionCookieConfig.getMaxAge());
                if (sessionCookieConfig.getDomainPattern() != null)
                    cookieSerializer.setDomainNamePattern(sessionCookieConfig.getDomainPattern());
                if (sessionCookieConfig.getJvmRoute() != null)
                    cookieSerializer.setJvmRoute(sessionCookieConfig.getJvmRoute());
                cookieSerializer.setUseSecureCookie(sessionCookieConfig.isSecure());
                cookieSerializer.setUseBase64Encoding(sessionCookieConfig.isUseBase64Encoding());
                cookieSerializer.setUseHttpOnlyCookie(sessionCookieConfig.isHttpOnly());
            }
        }
        if (ClassUtils.isPresent(
                "org.springframework.security.web.authentication.RememberMeServices",
                null)) {
            boolean usesSpringSessionRememberMeServices = !ObjectUtils
                    .isEmpty(this.context
                            .getBeanNamesForType(SpringSessionRememberMeServices.class));
            if (usesSpringSessionRememberMeServices) {
                cookieSerializer.setRememberMeRequestAttribute(
                        SpringSessionRememberMeServices.REMEMBER_ME_LOGIN_ATTR);
            }
        }
        return cookieSerializer;
    }

    private ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = applicationContext;
    }
}

c) 修改配置文件配置

在这里插入图片描述

d) 配置完成后重新启动, 再看 SpringHttpSessionConfiguration 加载的时候, 拿到了我们自定义的 CookieSerializer, 我想要的配置都有了!! 打开浏览器测试通过!!
在这里插入图片描述
在这里插入图片描述

第二种方案: 拿到 Cookie 中的 sessionId 后, 手动解码, 再 通过 sessionRespositry.findById(sessionId); 获取session

a) 解码的方案 从 DefaultSerializer 类中 copy 一个 , 如下:

/**
 * Decode the value using Base64.
 * @param base64Value the Base64 String to decode
 * @return the Base64 decoded value
 * @since 1.2.2
 */
private String base64Decode(String base64Value) {
    try {
        byte[] decodedCookieBytes = Base64.getDecoder().decode(base64Value);
        return new String(decodedCookieBytes);
    }
    catch (Exception e) {
        return null;
    }
}

b) 获取步骤:

String cookieSessionId = “XXX”;

String sessionId = base64Decode(cookieSessionId);

Session session = sessionRespositry.findById(sessionId);

c) 搞定! (此方案未解决 httpOnly 不起效的问题, 如果要解决 httpOnly = false , 请看方案一)

### CAS 登录 URL 中包含 SessionID 的处理方法 当涉及到 CAS (Central Authentication Service) 实现单点登录时,URL 中携带 `JSESSIONID` 可能会引发一系列安全性和功能性问题。具体来说,在某些配置环境下,CAS 客户端可能会尝试通过 URL 参数传递 session ID 给服务端[^5]。 #### 解决方案概述 为了防止这种情况发生并提高系统的安全性,可以采取以下措施: 1. **修改 Tomcat 设置** 对于运行在 Tomcat 上的应用程序而言,可以通过调整其配置文件来控制 Cookie 行为。编辑 `$CATALINA_BASE/conf/context.xml` 文件,设置 `<Context>` 元素属性如下所示: ```xml <Context cookies="true" useHttpOnly="true"> <!-- Other configurations --> </Context> ``` 2. **禁用 URL 重写** 确保应用程序会因为缺少支持 Cookies 浏览器而启用 URL 重写功能。这通常意味着要确保所有的客户端都正确设置了接受 Cookies 的能力,并且服务器端也相应地进行了适当配置以利用这一点。 3. **增强安全性** - 使用 HTTPS 来加密传输数据,从而减少中间人攻击的风险。 - 启用 HttpOnly 标志位,使得 JavaScript 无法访问这些敏感信息。 - 设定适当的 Secure 属性,指示浏览器仅应在 SSL/TLS 连接上发送此 cookie。 4. **代码层面优化** 如果仍然遇到问题,则可以在应用层面上做进一步改进。例如,在 Spring Security 或其他框架中自定义过滤器链,拦截任何试图将 session id 添加到 URL 的操作,并将其转换回基于 cookie 的管理方式。 ```java import javax.servlet.Filter; import org.springframework.security.web.session.SessionManagementFilter; @Bean public Filter springSecurityFilterChain() throws Exception { // ... other filters ... SessionManagementFilter smf = new SessionManagementFilter(securityContextRepository); smf.setAlwaysRespectSessionCreationPolicy(true); http.addFilter(smf).build(); } ``` 上述做法有助于确保即使是在不同域之间跳转的情况下也能维持一致的安全策略。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值