问题起源
场景1: 在微服务场景下,用户登录是通过认证服务来完成的,登录成功会将sessionId存在cookie中,但是当登录成功跳转到首页时并不能获取到session信息,原因就是首页在另外一个服务中,此时cookie出现了跨域问题获取不到。
场景2: feign远程调用时,也会丢失cookie,因为feign自己构造了一个新的请求,这个请求里面没有任何请求头。在异步编排时,feign还会丢失上下文信息,因为老请求的信息存在threadlocal里,开启多线程时,就获取不到老请求的信息了
cookie写入问题
解决cookie写入问题,要注意两点:
- cookie中的domain域必须和地址栏(或者是父域名)一致。
- cors跨域满足携带cookie的生效条件
- 服务的响应头中需要携带Access-Control-Allow-Credentials并且为true。(网关中已设置)
- 响应头中的Access-Control-Allow-Origin一定不能为*,必须是指定的域名。(网关中已设置具体域名)
- 浏览器发起ajax需要指定withCredentials 为true。(前端工程:gmall-admin\src\utils\httpRequest.js文件已经设置)
场景1原因
在获取cookie的作用域,也就是getDomainName方法中,其中获取的serverName是ip地址。并不是我们提供的域名
这是因为在地址栏输入域名时,经过了两次转发:
- 我们使用了nginx反向代理,当监听到www.gulimall.com的时候,会自动将请求转发至代理ip地址,即gateway服务器地址。
- 而后请求到达我们的gateway网关,gateway网关就会根据路径匹配
而这两次转发都会丢失域名信息。
场景1解决1
写一个session的配置,扩大cookie的作用域,原来的作用域为auth.gulimall.com,现在扩大为gulimall.com
@Configuration
public class GulimallSessionConfig {
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
//放大作用域
cookieSerializer.setDomainName("gulimall.com");
cookieSerializer.setCookieName("GULISESSION");
return cookieSerializer;
}
@Bean
public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
}
解决2
1. nginx转发时要携带域名
需要在nginx配置文件中配置代理头信息:
proxy_set_header Host $host;
修改完成之后,重启nginx
2. 网关转发时要携带域名
在网关转发请求给服务时,要携带地址信息:
spring.cloud.gateway.x-forwarded.host-enabled=true
修改之后再次重启,测试
发现serverName依然时ip地址。
这是因为网关转发后,会把域名通过X-Forwarded-Host头信息转发给服务。所以,需要修改代码,如下:
String serverName = request.getHeader("X-Forwarded-Host");
场景2解决
自定义feign的拦截器,给新请求同步老请求的cookie
@Configuration
public class GuliFeignConfig {
@Bean("requestInterceptor")
public RequestInterceptor requestInterceptor() {
RequestInterceptor requestInterceptor = new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
//1、使用RequestContextHolder拿到刚进来的请求数据
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes != null) {
//老请求
HttpServletRequest request = requestAttributes.getRequest();
if (request != null) {
//2、同步请求头的数据(主要是cookie)
//把老请求的cookie值放到新请求上来,进行一个同步
String cookie = request.getHeader("Cookie");
template.header("Cookie", cookie);
}
}
}
};
return requestInterceptor;
}
}
异步编排时,每一个线程都来共享之前的请求数据
//先拿到老请求的信息
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
//第一个异步任务
CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() -> {
//每一个线程都来共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
List<MemberAddressVo> address = memberFeignService.getAddress(memberResponseVo.getId());
confirmVo.setMemberAddressVos(address);
}, threadPoolExecutor);
//第二个异步任务
CompletableFuture<Void> cartInfoFuture = CompletableFuture.runAsync(() -> {
//每一个线程都来共享之前的请求数据
RequestContextHolder.setRequestAttributes(requestAttributes);
List<OrderItemVo> currentCartItems = cartFeignService.getCurrentCartItems();
confirmVo.setItems(currentCartItems);
}, threadPoolExecutor);