背景:
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); }

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