- A+
本次项目使用了react框架,同时使用fetch取代ajax作为获取接口数据的交互方法。本以为过程中应该不会有什么磕绊,没想到遇到了session丢失这个让人甚是苦恼的问题。期间本想换种方法来对接接口,但转念一想如果每次遇到问题都选择逃避,那么以后的编码之路只能越走越窄,所以还是决定坚持下去。好在经过一整天的摸索,总算是成功攻克了这个难关,下面就对这次问题的解决做个总结。
首先,为什么会出现postman接口调试正常而程序里fetch调用却出现session丢失的问题?
回顾fetch本身的特性——默认情况下, fetch在服务端不会发送或接收任何 cookies!!
再来看session——当客户端第一次请求服务器的时候,服务器生成一份session保存在服务端,将该数据(session)的id以cookie的形式传递给客户端;以后的每次请求,浏览器都会自动的携带cookie来访问服务器(session数据id)。
这就意味着fetch这种方法如果想要携带cookie,需要经过特殊的处理——credentials: "include",代码如下:
1 fetch("https://ip:端口/api/Values/GetVerifyCode", { 2 3 method: "GET", 4 5 credentials: 'include', 6 7 mode: 'cors', 8 9 headers: { 10 11 'Content-Type': 'application/json' 12 13 } 14 15 }).then(res => res.json()) 16 17 .then(res => { 18 19 if (res.code == 200) { 20 21 //xxxxxxx 22 23 } 24 25 });
这里还涉及到前后台分离时经常遇到的跨域问题(mode: 'cors'),什么是跨域?浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。在请求的过程中我们要想回去数据一般都是post/get请求,所以..跨域问题就出现。这是出于浏览器的同源策略限制。(节选自https://zhuanlan.zhihu.com/p/425855609)
后台以.netcore webapi为例,需要在Startup.cs里配置好Cors,代码如下:
ConfigureServices里:
1 services.AddCors(options => 2 3 { 4 5 options.AddPolicy("any", builder => 6 7 { 8 9 builder.AllowAnyOrigin() 10 11 .AllowAnyMethod() 12 13 .AllowAnyHeader(); 14 15 });//跨域请求 16 17 });
Configure里:
1 app.UseCors("any");//跨域请求
这里要注意的是如果你在服务器端的web.config里已经配置过跨域相关参数了,这里在加的话程序运行时会报错(”*,*”…意思就是重复配置跨域参数),要使用credentials: 'include',需要将上面的允许来自所有域的跨域请求访问改为只允许特定域访问,同时允许Credentials,如下:
1 builder.WithOrigins("http://ip:端口", "http://localhost:3000") 2 3 .AllowAnyMethod() 4 5 .AllowAnyHeader() 6 7 .AllowCredentials();
本来进行到这里我以为问题已经解决了,在本地调试OK的情况下发布接口,然后调用发布好的服务器接口,却还是没有获取到想要的cookie。以验证码为例:一个fetch1生成验证码,并把它通过响应标头带回来,之后另一个fetch2调接口时把之前带回来的cookie再通过请求标头传到后台进行校验,打开控制台发现fetch2的请求标头并没有cookie值,而fetch1的响应标头里也没有Set-Cookie。
点开末尾的cookie才发现问题的所在:
这个SameSite是何许人也?
SameSite是浏览器请求中Set-Cookie响应头新增的一种属性,它用来标明这个 cookie 是否是“同站 cookie”,同站 cookie 只能在本域名中使用的cookie,不能作为第三方 cookie。Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。该属性起初是由Google 起草的一份草案用来来改进 HTTP 协议的。Chrome 计划将Lax变为默认设置。这时,网站可以选择显式关闭SameSite属性,将其设为None (对旧版浏览器无效,因为旧版浏览器没有该值) 。不过,前提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。(节选自https://www.cnblogs.com/w821759016/p/14595832.html)
了解了这个,便可以很快地对Startup.cs里的services.AddSession做出调整,如下:
1 services.AddSession(options => 2 3 { 4 5 options.Cookie.Name = ".AdventureWorks.Session"; 6 7 options.IdleTimeout = TimeSpan.FromSeconds(1800);//设置session的过期时间 8 9 options.Cookie.HttpOnly = true;//设置在浏览器不能通过js获得该cookie的值 10 11 options.Cookie.SameSite = SameSiteMode.None; 12 13 options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; 14 15 options.Cookie.IsEssential = true; 16 17 });
其中,SameSite设为None配合Secure设为SameAsRequest,但要注意这个只允许访问的接口为https接口,另外将SameSite设置为了Unspecified浏览器则会使用默认值lax,是没有效果的。
最后,将原先的http接口改为https,至于https认证所需的SSL证书怎么搞这里就不赘述了,将这些完成后再次尝试调用发布好的接口,fetch1里的响应标头以及fetch2里的请求标头就都能在控制台看见了,问题解决!