在springcloud微服务应用中,各微服务按传统方式获取的session是不同的,为实现各微服务共享session,spring-session提供了解决方案,对HttpSession重新实现,并将session存放于redis中,各微服务从redis中获取一致的session对象。
在网关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>
在网关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);
}
//......
}
在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
在网关的spring配置文件中,还需加入如下配置:
zuul:
sensitive-headers: #不过滤客户端的任何请求头(如:Cookie不会被过滤)
ignored-headers: #微服务之间的传递不过滤任何请求头(如:微服务之间传递Cookie不会被过滤)
经过上述配置已经能够实现非跨域(同源)情况下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;
}
(此步骤一般没有必要),可能某些版本的springboot或者在某些特殊情况下经过上述配置后,在跨域条件下session可以实现共享了,但是第一次访问session不能共享,第二次及以后的访问session才可以共享。这是是由于第一次访问时,session信息还没有被写入cookie,也就是客户端还没有存储session信息,请求时也就不会向服务器发送包含session信息的cookie,在微服务相互调用过程中也就不会传递cookie,这样每一个微服务都会创建新的session,session也就不共享了,而第一次请求响应时会将包含session信息的cookie发送给客户端,因此以后请求session是可以共享的。
这个问题的解决的办法有多种,比较简单的一个办法是:在访问客户端首页时自动默认向服务器发送第一次无请意义的请求,在服务端也为该请求配置一个不做任何业务处理的映射路径,接下来用户进行操作都会传递含有session信息的cookie了。
当前用户信息是否存在的验证
在实际开发中,需要在每一次请求时判断当前用户是否存在,可以通过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;
}
}