当前位置: 首页 > 工具软件 > mini-session > 使用案例 >

SpringCloud之session共享

谭敏学
2023-12-01

基本原理

在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>
    
  2. 在网关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);
    	}
    	//......
    }
    
  3. 在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
    
  4. 在网关的spring配置文件中,还需加入如下配置:

    zuul:
      sensitive-headers: #不过滤客户端的任何请求头(如:Cookie不会被过滤)
      ignored-headers: #微服务之间的传递不过滤任何请求头(如:微服务之间传递Cookie不会被过滤)
    
  5. 经过上述配置已经能够实现非跨域(同源)情况下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;
    }
    
  6. (此步骤一般没有必要),可能某些版本的springboot或者在某些特殊情况下经过上述配置后,在跨域条件下session可以实现共享了,但是第一次访问session不能共享,第二次及以后的访问session才可以共享。这是是由于第一次访问时,session信息还没有被写入cookie,也就是客户端还没有存储session信息,请求时也就不会向服务器发送包含session信息的cookie,在微服务相互调用过程中也就不会传递cookie,这样每一个微服务都会创建新的session,session也就不共享了,而第一次请求响应时会将包含session信息的cookie发送给客户端,因此以后请求session是可以共享的。

    这个问题的解决的办法有多种,比较简单的一个办法是:在访问客户端首页时自动默认向服务器发送第一次无请意义的请求,在服务端也为该请求配置一个不做任何业务处理的映射路径,接下来用户进行操作都会传递含有session信息的cookie了。

  7. 当前用户信息是否存在的验证
    在实际开发中,需要在每一次请求时判断当前用户是否存在,可以通过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;
        }
    
    }
    
    

案例参考

服务端案例
前端案例

 类似资料: