Spring Session產生的sessionid與cookies中的sessionid不一樣的問題 && httpOnly 設置不起作用的問題??


背景:

  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

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);

}
MySessionCookieConfig
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;
    }
}
MyDefaultSessionCookieConfig

  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;
    }
}
RedisSessionConfig

  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 , 請看方案一)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM