一次跨域问题的解决经历(samesite)
1. 问题描述生产和测试环境使用nginx做了反向代理,所以不存在跨域的问题,但是在本地研发环境,由于前后端分离,前端和后端在不同的电脑上开发,存在跨域问题。前端使用的是vue,后端使用的是springboot。在前后端都做了跨域的设置,前端的设置为://前端在vue的main文件全局添加一下代码:import axios from 'axios';axios.defaults.withCrede
1. 问题描述
生产和测试环境使用nginx做了反向代理,所以不存在跨域的问题,但是在本地研发环境,由于前后端分离,前端和后端在不同的电脑上开发,存在跨域问题。
前端使用的是vue
,后端使用的是springboot
。在前后端都做了跨域的设置,前端的设置为:
//前端在vue的main文件全局添加一下代码:
import axios from 'axios';
axios.defaults.withCredentials=true;
后端的设置为:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfigurer {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
// 允许cookies跨域
config.setAllowCredentials(true);
// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedOrigin("*");
// #允许访问的头信息,*表示全部
config.addAllowedHeader("*");
// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.setMaxAge(18000L);
// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
出现的问题是:登录的时候有一个图片验证码,前端请求验证码图片之后后端会将验证码存在session
中,但是在本地开发的时候无论怎样验证码都是错误的,通过打断点发现session
中没有拿到验证码,一直是null
。
//将验证码放入session javax.servlet.http.HttpSession
httpSession.setAttribute("image-code-" + type, capText);
//从session中取出验证码
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
@Configuration
public class ImageCodeFilter extends ZuulFilter {
@Value("xxx")
private String loginUrl;
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 101;
}
@Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
String requestUrl = ctx.getRequest().getRequestURI();
return requestUrl.matches(loginUrl);
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpSession httpSession = ctx.getRequest().getSession();
for (int i = 0; i < 2; i++) {
String code = (String) httpSession.getAttribute("image-code-" + i);
if (code != null) {
ctx.addZuulRequestHeader("image-code-" + i, code);
}
}
return null;
}
2.问题排查
刚开始我以为是跨域设置没有生效,但是一想接口可以正常访问,说明跨域的设置是生效的了,session
中拿不到存储的验证码,那么就可以定位问题在请求携带的cookie
上出了问题。通过浏览器的开发者模式发现请求没有携带cookie
,这就很让人疑惑了。
查看返回头
每次返回头里都有这个Set-Cookie
,这意味着吗,每次都是一个新的cookie
,那么问题就很明显了,由于每次请求没有cookie
,后端每次都返回一个新的cookie
,这个cookie
中记录的就是JSESSIONID
,对应服务端的session
。
在session
拿不到验证码的问题可以简单描述为:由于请求头中没有cookie
,所以服务端每次接受前端的请求都会生成一个新的session
,自然没有办法从上一个session
中拿到验证码,这个可以通过打印两个地方的sessionId
来验证。
httpSession.getId()
然后我就搜索了许多解决方案,都不行,一般的解决点都围绕在允许cookie
跨域这上面,但是回顾我们的前后端跨域设置,我们都设置了允许cookie
跨域,所以问题不在这里。
3. 我的问题解决方案
在再次排查请求信息的时候,我发现了一个浏览器的告警:
这里提到了一个叫samesite
的东西,这个简而言之就是:Chrome 51
开始,浏览器的 Cookie
新增加了一个SameSite
属性,用来防止 CSRF
攻击和用户追踪。
感兴趣的可以看下这篇博文:Cookie 的 SameSite 属性
我们需要设置这个cookie
的这个属性来允许浏览器跨域访问可以携带请求头,但是spring
对这个的支持是在spring-session 2.x
中,而我们的这个项目比较老,版本是springboot 1.x
,我尝试引入依赖并进行设置,但是并不起作用。
参考的文章:spring设置
现在,问题似乎又到了死胡同,我们再梳理一下,我们目前存在的问题是本地开发前后端联调无法携带cookie
跨域,导致联调无法进行,在测试和生产环境由于nginx
做了反向代理,解决了跨域的问题,自然也不存在这个问题,所以,我们只要在开发环境解决这个跨域请求携带cookie
的问题即可,无需关心测试和生产环境。既然这个samesite
是浏览器新版本新增的属性,那么我们关掉它不就可以了?试过之后,可行,成功获取session
中的验证码。浏览器设置如下:
在chrome中打开链接: chrome://flags/#site-isolation-trial-opt-out,搜索samesite,禁用前三个选项,然后重启浏览器
更多推荐
所有评论(0)