什么是跨域?

https://www.ruanyifeng.com/blog/2016/04/same-origin-policy.html https://www.ruanyifeng.com/blog/2016/04/cors.html 第16讲:浏览器同源策略与跨域方案详解

浏览器为了安全遵循同源策略(域名+端口+协议),控制不同源之间的交互
解决跨域问题 - 图1
如果非同源,共有三种行为受到限制。
(1) Cookie、LocalStorage 和 IndexDB 无法读取。
(2) DOM 无法获得。
(3) AJAX 请求不能发送。

解决跨域

请求跨域

CORS

跨域资源共享(CORS,Cross-Origin Resource Sharing)是浏览器为 AJAX 请求设置的一种跨域机制,让其可以在服务端允许的情况下进行跨域访问。主要通过 HTTP 响应头来告诉浏览器服务端是否允许当前域的脚本进行跨域访问。

简单请求和非简单请求

跨域资源共享将 AJAX 请求分成了两类:简单请求和非简单请求。其中简单请求符合下面 2 个特征。

  • 请求方法为 GETPOSTHEAD
  • 请求头只能使用下面的字段:
    • Accept
    • Accept-Language
    • Content-Type (只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded)
    • Content-Language

任意一条要求不符合的即为非简单请求,非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或DELETE,或者Content-Type字段的类型是application/json等。

处理简单请求

对于简单请求,处理流程如下:

  • 浏览器发出简单请求的时候,会在请求头部增加一个 Origin字段,对应的值为当前请求的源信息;
  • 当服务端收到请求后,会根据请求头字段 Origin做出判断后返回相应的内容。
  • 浏览器收到响应报文后会根据响应头部字段Access-Control-Allow-Origin 进行判断,这个字段值为服务端允许跨域请求的源,其中通配符“*”表示允许所有跨域请求。如果头部信息没有包含Access-Control-Allow-Origin 字段或者响应的头部字段 Access-Control-Allow-Origin不允许当前源的请求,则会抛出错误。


    处理非简单请求

    当处理非简单的请求时,浏览器会先发出一个预检请求(Preflight)。这个预检请求为 OPTIONS 方法,并会添加了 1 个请求头部字段Access-Control-Request-Method,值为跨域请求所使用的请求方法。

如果添加了不属于上述简单请求的头部字段,所以浏览器在请求头部添加了Access-Control-Request-Headers 字段,值为跨域请求添加的请求头部字段 authorization。

在服务端收到预检请求后,除了在响应头部添加 Access-Control-Allow-Origin 字段之外,至少还会添加Access-Control-Allow-Methods 字段来告诉浏览器服务端允许的请求方法,并返回 204 状态码。

一旦服务器通过了”预检”请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

JSONP

JSONP(JSON with Padding)的大概意思就是用 JSON 数据来填充,怎么填充呢?结合它的实现方式可以知道,就是把 JSON 数填充到一个回调函数中。这种比较 hack 的方式,利用的是 script 标签跨域引用 js 文件不会受到浏览器同源策略的限制。

  1. function jsonp({url,params,cb}) {
  2. return new Promise((resolve,reject)=>{
  3. // 处理传参成x=a&y=b的形式
  4. params = {...params,cb}
  5. let arrs = [];
  6. for(let key in params){
  7. arrs.push(`${key}=${params[key]}`);
  8. }
  9. let script = document.createElement('script'); // 标签
  10. window[cb] = function (data) {
  11. resolve(data);
  12. document.body.removeChild(script);
  13. }
  14. script.src = `${url}?${arrs.join('&')}`;
  15. document.body.appendChild(script);
  16. });
  17. }
  18. // 只能发送get请求 不支持post put delete
  19. // 不安全 xss攻击 不采用
  20. jsonp({
  21. url: 'http://localhost:3000/say',
  22. params:{wd:'我爱你'},
  23. cb:'show'
  24. }).then(data=>{
  25. console.log(data);
  26. });

自写服务端

  1. let express = require('express');
  2. let app = express();
  3. app.get('/say',function (req,res) {
  4. let {wd,cb} = req.query;
  5. console.log(wd);
  6. res.end(`${cb}('我不爱你')`)
  7. })
  8. app.listen(3000);

代理转发

在服务端进行跨域,比如设置代理转发

webpack-dev-server

  1. // webpack.config.js
  2. module.exports = {
  3. //...
  4. devServer: {
  5. proxy: {
  6. '/api': 'http://localhost:3000'
  7. }
  8. }
  9. };

在 Nginx 服务器上配置同样的转发规则也非常简单,下面是示例配置。

  1. location /api {
  2. proxy_pass http://localhost:3000;
  3. }

通过 location 指令匹配路径,然后通过 proxy_pass 指令指向代理地址即可。

页面跨域解决方案

除了浏览器请求跨域之外,页面之间也会有跨域需求,例如使用 iframe 时父子页面之间进行通信。

postMessage

  1. // https://lagou.com
  2. var child = window.open('https://kaiwu.lagou.com');
  3. child.postMessage('hi', 'https://kaiwu.lagou.com');
  4. // https://kaiwu.lagou.com
  5. window.addEventListener('message', function(e) {
  6. console.log(e.data);
  7. },false);

改域

对于主域名相同,子域名不同的情况,可以通过修改 document.domain 的值来进行跨域。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查。

比如,有一个页面,它的地址是 https://www.lagou.com/parent.html,在这个页面里面有一个 iframe,其 src 是 http://kaiwu.lagou.com/child.html
这时只要把 http://www.lagou.com/parent.htmlhttp://kaiwu.lagou.com/child.html 这两个页面的 document.domain 都设成相同的域名,那么父子页面之间就可以进行跨域通信了,同时还可以共享 cookie。

但要注意的是,只能把 document.domain 设置成更高级的父域才有效果

例如在 http://kaiwu.lagou.com/child.html 中可以将 document.domain 设置成 lagou.com。

window.name

window.name的值是跟着浏览器窗口走的,因此,只要在一个窗口中,就可以共享一个值,于是可以实现跨域数据获取