一、引言
http是无状态协议,客户端若想在多次访问服务端的过程中携带状态信息通常需要采取以下几种方式:
对于Session方式而言,Session信息通常保存在Web容器中,若系统由多个应用组成,每个应用处于不同的Web容器,则当请求在不同应用之间传递时由于Web容器不同导致Sesison信息不一致,因此需要使用Session共享、Session复制等机制保证Session一致性。
本文讨论的是使用SpringSession将多个应用的Session保存在同一个Redis缓存从而实现Session共享的情况。
二、问题
SpringSession默认使用Cookies保存和传递SessionId,在单WebApp情况下,SessionId可以正常传递,但在多WebApp时SessionId则无法正常保存和传递,仍然会导致Session失效。这是典型的Cookies跨域导致的问题。
查看SpringSession源码,找到SpringSession的主Filter类
public class SessionRepositoryFilter<S extends ExpiringSession> extends OncePerRequestFilter { ... // 此处可以看出默认使用的Cookies方式携带SesisonId private MultiHttpSessionStrategy httpSessionStrategy = new CookieHttpSessionStrategy(); ... } public final class CookieHttpSessionStrategy implements MultiHttpSessionStrategy, HttpSessionManager { ... // 此处表明使用CookieSerializer读写Cookies中的SessionId信息 private CookieSerializer cookieSerializer = new DefaultCookieSerializer(); ... // 此处表明cookieSerializer可以通过Spring注入方式改变实现类 public void setCookieSerializer(CookieSerializer cookieSerializer) { Assert.notNull(cookieSerializer, "cookieSerializer cannot be null"); this.cookieSerializer = cookieSerializer; } ... } public class DefaultCookieSerializer implements CookieSerializer { ... public void writeCookieValue(CookieValue cookieValue) { ... // 此处设置Cookies的有效路径 sessionCookie.setPath(getCookiePath(request)); ... }
} private String getCookiePath(HttpServletRequest request) { if (this.cookiePath == null) { // 此处看出每个WebApp的Cookies路径不同,所以WebApp之间Cookies无法相互访问,是SessionId丢失的问题所在 return request.getContextPath() + "/"; } return this.cookiePath; } ... }
三、解决方案
上一节已经指出由于Cookies路径不同,导致WebApp之间Cookies无法相互访问,现在来看一下问题解决思路。
由于CookieHttpSessionStrategy默认使用的是DefaultCookieSerializer,我们可以定义我们自己的CookiesSerializer命名为CustomerCookiesSerializer,并注入到CookieHttpSessionStrategy中。CustomerCookiesSerializer需要重新定义getCookiePath方法,并且将返回路径改为/根路径,这样各WebApp之间就可共享Cookies了。
具体方法如下:
1. 定义CustomerCookiesSerializer
public class DefaultCookieSerializer implements CookieSerializer { // 此处拷贝DefaultCookieSerializer中的代码
// 修改getCookiePath方法体 private String getCookiePath(HttpServletRequest request) { if (this.cookiePath == null) { // 此处改为返回根路径 return "/"; } return this.cookiePath; } }
2. 注入CustomerCookiesSerializer
通过Spring的注解方式注入CustomerCookieSerializer到CookieHttpSessionStrategy中
@Configuration @EnableRedisHttpSession public class HttpSessionConfig { @Bean public CookieHttpSessionStrategy cookieHttpSessionStrategy() { CookieHttpSessionStrategy strategy = new CookieHttpSessionStrategy(); strategy.setCookieSerializer(newCustomerCookieSerializer()); return strategy; } }
3. 重启各WebApp即可。
本文原创,转载请注明出处。