跨域方法总结

基本概念

浏览器出于安全的考虑,引入了同源策略。这种策略会对我们页面上执行的js访问资源的时候进行限制,比如我们不能直接通过js访问不同源之下的页面DOM结构,同时在对不同源发送请求时也无法获取到服务器响应内容(服务器会正常处理请求并返回响应内容,但是返回的内容被浏览器拦截掉了)。这里还牵扯到“源”这个概念,如果我们访问的目标url和当前页面所在的url两者的协议、域名、端口只要有一个不相同,那么就认为是属于两个不同的源。明白了源的定义之后,我们再来看看在同源策略的作用下,我们可以在页面上做的以及不能做的都有哪些操作。

先说能够做的,比如通过js重定向我们的页面(修改location.href),表单提交,这些都是可以的。还有就是通过嵌入一些HTML标签来加载我们需要的资源,比如script标签引入一段脚本、img标签插入一张图片、link标签加载样式文件、iframe嵌入不同源的页面等等也都是可以的。

但是,同源策略对js访问一些敏感资源则进行了限制。除了开头提到的那两点之外,还有就是js中无法访问不属于同个源的cookieLocalStorage中存储的内容。具体来说,cookie和LocalStorage在控制哪些源可以访问的问题上还是细微的差别,父域在设置cookie的时候可以设定允许子域访问这段cookie,同时Cookie只和域名以及路径关联,如果是同个域名不同端口的源依然是共享同个域名下的Cookie,而LocalStorage则是以源为单位进行管理,相互独立,不同源之间无法相互访问LocalStorage中的内容。

跨域方法

CORS

这种方法应该是用得比较多的一种。CORS全称是Cross-Origin Resource Sharing,翻译过来就是跨域资源共享。基本思想就是引入一些自定义的HTTP Header来完成客户端与服务端的通信。

对于一些简单请求,浏览器在发送请求时会带上Origin请求头,指示当前的源,服务器端在处理请求时不会去检查当前请求来源是否合法,依然会正常处理请求并响应,最终浏览器在拿到响应之后会检查服务端响应的Access-Control-Allow-Origin列表中是否存在当前页面所在的源,如果不存在会直接block掉当前请求。

在浏览器看来,同时满足以下条件的请求都认为是简单请求:

  1. 请求方法为GET或者POST;
  2. 只包含Accept、Accept-Language、Content-Language或者Content-Type(取值为application/x-www-form-urlencoded, multipart/form-data, 或者 text/plain),其余情况的Header则属于非简单Header;

对于非简单请求,浏览器会先向服务器发送一个Preflight请求,该请求使用Option方法,并包含以下Header:

  1. Origin
  2. Access-Control-Request-Method:询问服务器是否支持某方法;
  3. Access-Control-Request-Headers:询问服务器是否支持请求中包含的非简单Header;

其中后两个Header只会出现在Preflight请求中。然后浏览器收到包含以下Header的服务器响应:

  1. Access-Control-Allow-Origin
  2. Access-Control-Allow-Methods:对客户端回应服务器支持的请求方法列表;
  3. Access-Control-Allow-Headers:对客户端回应服务器支持的Header;

Preflight请求至此也算是告一段落,之后浏览器会检查当前请求发出的源是否在服务端响应的Access-Control-Allow-Origin列出的源的列表中,如果是才会发送真正的请求。在实验过程中,浏览器并不一定要在服务器支持Preflight请求查询的请求方法和Header时才发送真正的请求,只要发出请求的源是合法的就会在Preflight请求之后把请求发出去。

JSONP

JSONP 的全称是 JSON with Padding,译为被填充的JSON。前端在指定要请求的URL时可以通过和后端约定一个指定回调函数名称的参数,确保后台响应的脚本片段中调用了前端指定的回调函数,以此可以实现发送多个JSONP请求而且互不干扰。具体JSONP实现如下:

JSONP这种方式本身也是存在一定缺陷的,很明显它只能用于GET请求。另外,后端应用程序在处理过程可能会出现4xx(对于客户端错误也需要返回回调脚本,并将错误信息填充到回调参数中)、5xx错误或者遇到其他意外情况,导致无法返回正确的js函数调用格式的字符串的情况,所以还需要监听script标签的onerror事件来处理可能出现的意外情况。

cookie作为客户端存储的一种方案,在客户端设置cookie也有以下几种方法:

  1. 配置服务端返回Set-Cookie响应头;
  2. 在页面上的JavaScript代码中通过document.cookie,直接设置document.cookie为我们需要存储的内容并不会覆盖现有的cookie,cookie的格式:

通过JavaScript设置当前域的cookie,如果服务端对某条cookie设置了HttpOnly属性,在JavaScript中将无法访问这段cookie,但是JavaScript依然可以设置新的cookie,举例:

cookie是有过期时间的,指定过期时间可以通过Expires(设置过期的时间点)或者Max-Age(设置生效时长)来设置(过期时间是相对客户端来计算的);如果没有显式设置过期时间,则该cookie会在浏览器关闭时失效。

cookie如果设置Secure属性,则该cookie只能通过https发送到服务端。

URL与设置cookie的域名和路径匹配的页面才能访问设置在该路径下的cookie并且被发送到服务端,其他不匹配的页面则访问不到。如果cookie没有设置对应的Domain,则默认cookie只能发送到当前页面所在的域,但是不包括子域。如果设置了Domain,则子域默认也会被包含在内。如果cookie没有设置作用路径,则设置的cookie默认对当前域下所有路径都可见,意味着访问任何页面都会携带这段cookie。

通过Path设置的cookie的有效路径表示一个目录或者一个文件路径。举例:

一般情况下,浏览器在访问页面时会自动将和当前域名以及路径匹配的Cookie发送到服务器端。而在页面中发出的Ajax请求则不会自动将与请求的URL关联的Cookie同请求一同发送到服务端。如果我们需要在请求中将和当前访问的URL的Cookie发送到服务端,可以设置XMLHttpRequest对象的withCredentials属性为true。

而如果是要将父域设置的Cookie发送到其中的一个子域(或者父域的其他端口)的服务端,这时候需要对服务端进行配置,使其支持CORS,同时还需要注意此时服务端返回的Access-Control-Allow-Origin不能再设置为‘*’,同时服务端需要返回Access-Control-Allow-Credentials: true,否则服务端的响应依然会被浏览器block掉。在这样配置之后,浏览器在发送这种携带凭据信息(也就是Cookie)的Ajax请求时就会把当前页面所在的域名下path属性和请求的URL相匹配(比如当前请求的URL为/test/example,那么设置在/,/test/,/test/example这些path之下的Cookie会被发送)的Cookie一同发送到服务端,这样就实现了Cookie的跨域共享。

cookie在客户端的容量限制:每个域最多可以设置20个cookie,每条cookie的大小不超过4KB,当某个域在客户端存储的cookie数量超出限制,则浏览器会对其中部分cookie进行删除。

cookie可以分为两种:一种是第一方cookie,一种是第三方cookie。第一方cookie指的是由当前页面的域名设置的cookie,而第三方cookie则是由页面上嵌入的第三方(即来自不同域)资源在加载时由第三方设置的cookie,然后可以由第三方获取。一些广告商通常会在多个网站投放广告,当用户访问这些网站时,广告商可以通过设置的第三方cookie来追踪用户,导致用户隐私泄露。

其他跨域问题

  1. 字体文件加载

CSS中引用的不同源的字体文件加载也存在跨域问题,需要设置CORS才能加载其他域下的字体文件。默认情况下定义新的字体不会立即去下载对应的字体文件,只有当页面上的元素使用了这种字体才会去下载对应的字体文件(只是定义了这种字体也不会下载字体文件)。

2. 跨域脚本错误处理

对于页面上加载的跨域脚本执行出错,页面上绑定的错误处理函数window.onerror在默认情况下是获取不到具体的错误信息的,这时候需要在加载跨域脚本的标签上使用crossorigin属性,也就是在请求跨域脚本的时候执行CORS。crossorigin属性可以设置的值有:

  • anonymous:请求脚本的时候不会携带凭据
  • use-credentials:请求脚本的时候携带凭据

设置为其他值都会被看作是anonymous关键字。设置了crossorigin属性意味着还需要对服务器进行配置,使其支持CORS。如果服务端没有正确配置CORS,跨域脚本是无法正常下载的。

3. canvas绘制内容转化为文件对象

canvas中动态加载的图片可以直接画到canvas中,但是在将canvas转化成文件对象进行操作时也存在跨域问题,会遇到“Tainted canvases may not be exported”错误。这时候需要对动态加载的图片对象设置crossOrigin属性,同时也需要配置服务器使其支持CORS。

4. websocket不受同源策略的限制,没有跨域的问题

5. 表单同样也允许跨域提交,站点预防通过表单提交的恶意请求的方法其实和防御CSRF攻击类似,需要增加验证机制