0%

请求跨域以及传递Cookie

以前涉及到的网站项目都是前后端在一块儿的,这个在一块儿指的是同一个服务器同一个域名下,访问的时候由php进行跳转页面、控制页面的输出,JavaScript顶多发个ajax请求数据,不涉及到前端自己保存登录状态,也不会涉及到跨域问题。

随着前端的发展,特别是nodejs的出现后,由nodejs启动服务,前端可以不依赖后端,前端自己跑自己的业务,然后在需要数据的时候发个ajax去调用后端接口去取数据,所以这就是前后端分离,前端专注前端的业务,后端专注于提供自己的接口。
在前后端分离的基础上,前端和后端有可能不是同一个服务器、不是同一个域名,这时候就会有跨域的问题。

那什么是跨域?

跨域指的是“跨域资源共享(Cross-Origin Resource Sharing,CORS)”。

当一个资源从与改资源本身所在服务器的不同的域或不同的端口请求一个资源时,资源会发起一个跨域HTTP请求。

跨域的情况:

  • 同一域名不同端口,比如http://www.demo.com:80http://www.demo.com:8080
  • 同一域名不同协议,比如http://www.demo.comhttps://www.demo.com
  • 域名和域名对应ip,比如http://www.demo.comhttp://192.168.2.3
  • 同一域名,不同二级域名,比如http://blog.demo.comhttp://blog2.demo.comhttp://demo.com(这种情况cookie也无法传递)
  • 不同域名,比如http://demo.comhttp://demo2.com

跨域资源共享定义了必须在访问跨域资源时,浏览器与服务器改如何沟通,背后的基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定成功还是失败。

跨域资源共享允许向跨域服务器发出XMLHttpRequest请求,从而克服了ajax只能同源使用的限制。

整个CORS通信过程,都是浏览器自动完成的,不需要用户参与。对开发者来说,CORS通信与同源的AJAX通信没有差别。浏览器一旦发现AJAX请求跨域,就会自动添加一些附加的头信息,但用户不会察觉。

两种请求

浏览器将CORS请求分为两类:简单请求,非简单请求。

简单请求的条件:

  1. 请求方法是下三种方法之一:HEADGETPOST
  2. HTTP的头信息不超出一下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-Type(只限于三个值:application/x-www-form-urlencodedmultipart/form-datatext/plain

凡是不满足上面两个条件的,都属于非简单请求。

简单请求

第一步:浏览器发起请求给服务器

对于简单请求,浏览器直接发出CORS请求,会在头信息中增加一个Origin字段用来说明,本请求来自于哪个源(协议+域名+端口)。服务器根据这个值来决定是否同意这次请求。

1
2
Host:localhost:8080
Origin:http://localhost:3000

第二步:服务器收到请求并返回给浏览器

如果服务器没做任何设置,这时候是无法访成功的,因为跨域了,浏览器会收到200的状态码,但是并没有返回任何东西,并且浏览器自己也会抛出错误:

1
XMLHttpRequest cannot load http://localhost:8080/tp-api/account.php/login/login. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.

意思是服务器不允许访问,因为请求资源上不存在访问控制允许源的标头。

这时候在服务器上PHP文件添加:

1
header("Access-Control-Allow-Origin: *"); //*号表示所有域名都可以访问

这样再访问就可以了,数据都正常返回。

但是,浏览器没有发送cookie给服务器,而服务器的响应报文中包含着Set-Cookie字段,并且里面包含的PHPSESSID每次请求都不同。用户的登录状态信息是存在服务器中的session里面的,浏览器和session匹配就靠cookie中带着的phpssessid,如果无法传递cookie就无法获取登录信息。这是个很尴尬的问题,先留个伏笔。

至少,现在跨域请求是可以了。

与CORS相关的三个字段

Access-Control-Allow-Origin

定义在服务器端,它定义可以接受的请求的源,如果值是*就表示可以接受任意来源域的请求。

1
2
header("Access-Control-Allow-Origin: http://account.tcs-y.com"); //只接受http://account.tcs-y.com的访问
header("Access-Control-Allow-Origin: *"); //*号表示所有域名都可以访问

Access-Control-Allow-Credentials

定义在服务器端,表示是否允许发送cookie,默认情况下cookie不包括在CORS请求中。如果我们希望浏览器发送cookie,那么需要明确表示许可,也就是需要将该字段设置为true

1
header("Access-Control-Allow-Credentials:true");// 允许浏览器发送cookie

Access-Control-Expose-Headers

定义在服务器端,Expose的意思是“暴露”,所以这个字段的意思是定义可以暴露的标头。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers字段里面指定。

在跨域请求中发送cookie

前面说到,添加了头标Access-Control-Allow-Origin之后,是可以进行跨域请求了,但是浏览器无法传递cookie,并且每次访问会接收到服务器传过来的cookie,并且里面的phpsessid每次都不一样,这样我们没法告诉服务器我们已经等了过了。

介绍了上面三个字段后,我们知道了如果浏览器要传递cookie,需要在服务器打开Access-Control-Allow-Credentials,这时候我们访问服务器,发现浏览器并没有传递cookie,这是为啥?

因为默认情况下CORS请求不发送Cookie和HTTP认证信息,如果需要把Cookie发送到服务器,不仅仅需要在服务器接受,而且需要在ajax请求中打开withCredentials属性,让请求中带着cookie。否则,即使服务器同意接收cookie,浏览器也不会发送。

ok,我们在发送ajax的时候设置withCredentials这个属性:

1
2
3
4
5
6
$http({
method: method,
url: url,
params: params,
withCredentials: true,
.....

我们再发起请求,发现请求状态是200,但是依旧没有返回值,而且浏览器又报了个错:

1
XMLHttpRequest cannot load http://localhost:8080/tp-api/account.php/account/AccountList. The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:3000' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

意思是,当请求的凭据模式为“include”时,响应中的Access-Control-Allow-Origin不能为*

withCredentials的值为true的时候,浏览器会携带用户凭证,这时候需要服务器明确指定接收的源,而不能是通配符*

所以,最后汇合在一起,如果需要跨域访问,并传递和接收cookie,需要设置的步骤为:

  • 前端发起ajax的时候,设置withCredentials:true,让浏览器发送请求的时候带着cookie
  • 后端接受请求的时候,设置
    1
    2
    3
    4
    // 让服务器接受cookie
    header("Access-Control-Allow-Credentials:true");
    //可接受的源里面包含要发过来cookie的源。
    header("Access-Control-Allow-Origin: http://account.tcs-y.com");
    大功告成!!

后续补上非简单请求。

码字辛苦,打赏个咖啡☕️可好?💘