下SpringCloud之session共享

  • 基本原理
  • 解决方案要点
  • 案例参考


基本原理

在springcloud微服务应用中,各微服务按传统方式获取的session是不同的,为实现各微服务共享session,spring-session提供了解决方案,对HttpSession重新实现,并将session存放于redis中,各微服务从redis中获取一致的session对象。

解决方案要点

  1. 在网关zuul和各微服务中引入如下依赖
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.session</groupId>
	<artifactId>spring-session-data-redis</artifactId>
</dependency>
  1. 在网关zuul组件和各微服务组件的配置类(或者启动类)上加注解@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)启用缓存于redis的springsession,其中flushMode = FlushMode.IMMEDIATE表示session新建或属性发生变化时立即持久化,即当session发生变化时各组件立即感知。
    配置示例如下:
@SpringBootApplication
@EnableRedisHttpSession(flushMode = FlushMode.IMMEDIATE)
//......
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	//......
}
  1. 在spring配置文件application.yaml中配置redis,示例如下:
spring:
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    password:
    timeout: 1500
    jedis:
      pool:
        max-idle: 10
        max-wait: -1ms
        min-idle: 2
  1. 在网关的spring配置文件中,还需加入如下配置:
zuul:
  sensitive-headers: #不过滤客户端的任何请求头(如:Cookie不会被过滤)
  ignored-headers: #微服务之间的传递不过滤任何请求头(如:微服务之间传递Cookie不会被过滤)
  1. 经过上述配置已经能够实现非跨域(同源)情况下session的共享。但是,经过在Springboot2.2.x环境下测试,在跨域情况下还是无法实现session共享(这可能与springboot版本有一定关系),经在网上查资料找到原因,这是因为springsession使用CookieSerializer对象设置cookie时候,其默认的同源策略(SameSite)为lax,导致在跨域请求时除get方式外都不会向服务器发送cookie,解决办法是在各微服务中配置一个新的CookieSerializer对象,将同源策略设置为null,即在配置类(或者启动类)中添加如下配置:
@Bean
public CookieSerializer cookieSerializer() {
	DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
	cookieSerializer.setSameSite(null);
	return cookieSerializer;
}
  1. (此步骤一般没有必要),可能某些版本的springboot或者在某些特殊情况下经过上述配置后,在跨域条件下session可以实现共享了,但是第一次访问session不能共享,第二次及以后的访问session才可以共享。这是是由于第一次访问时,session信息还没有被写入cookie,也就是客户端还没有存储session信息,请求时也就不会向服务器发送包含session信息的cookie,在微服务相互调用过程中也就不会传递cookie,这样每一个微服务都会创建新的session,session也就不共享了,而第一次请求响应时会将包含session信息的cookie发送给客户端,因此以后请求session是可以共享的。
    这个问题的解决的办法有多种,比较简单的一个办法是:在访问客户端首页时自动默认向服务器发送第一次无请意义的请求,在服务端也为该请求配置一个不做任何业务处理的映射路径,接下来用户进行操作都会传递含有session信息的cookie了。
  2. 当前用户信息是否存在的验证
    在实际开发中,需要在每一次请求时判断当前用户是否存在,可以通过zuul 中 的过滤器实现,具体代码示例如下:
@Component
public class AuthenticationFilter extends ZuulFilter{
	
	private static final Logger LOG = LoggerFactory.getLogger(AuthenticationFilter.class);

    @Override
    public String filterType() {//过滤器类型,pre表示请求被处理之前拦截
        return "pre";
    }
 
    @Override
    public int filterOrder() {//执行过滤器的顺序号
        return 0;
    }
 
    @Override
    public boolean shouldFilter() {//过滤器是否生效
        return true;
    }
 
    @Override
    public Object run() {//拦截逻辑
        RequestContext currentContext = RequestContext.getCurrentContext();

        HttpServletRequest request = currentContext.getRequest();
        HttpServletResponse response = currentContext.getResponse();
        
        String path = request.getServletPath();
        
        LOG.debug("--------------------"+path+"-----------------------"); 
        
        
       
        
        if(path.startsWith("/safty-login")) {
        	return null;
        }
        

        
        HttpSession session = request.getSession();
        Object currUser = session.getAttribute(Constants.SESSION_ATTR_CURR_USER);

		if (currUser == null) {

			try {
				response.setContentType("application/json;charset=UTF-8");
				PrintWriter out = response.getWriter();
				
				out.print("{\"logined\":false}");
				
				out.flush();
				out.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			throw new RuntimeException();//抛出异常,阻止进一步处理
		}
        
        
        return null;
    }

}

案例参考

服务端案例前端案例