跨域原理和解决方案

前置知识

什么是跨域

主要是由于浏览器的同源策略引起的,同源策略是浏览器的安全机制,当 协议域名端口 三者有一个不同,浏览器就禁止访问资源。

比如:http://www.company.com:80

  • http://www.company.com:80/dir/page.html ----成功
  • http://www.child.a.com/test/index.html ----失败,域名不同
  • https://www.a.com/test/index.html ----失败,协议不同
  • http://www.a.com:8080/test/index.html ----失败,端口号不同
同源策略限制以下几种行为:
  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM和JS对象无法获得
  • AJAX 请求不能发送
不受同源策略限制的有:
  • 页面上的链接,比如 a 链接。
  • 重定向。
  • 表单提交。
  • 跨域资源的引入,比如:script, img, link, iframe

这里又有一个问题为什么表单提交不受同源策略限制?
所谓的同源策略,是一个域名在没有得到允许,不能够读取其他域名的内容。但浏览器不阻止你像其他域名发出请求。那么form发送请求能够跨域就可以理解,他只是发出请求,并没有得到响应。

首先,表单的提交方式有两种,一种是直接指定表单的action,一种是ajax接手控制请求。
直接使用action的时候,是直接把请求交给了action里面的域,本身页面不会去管他的请求结果,后面的步骤交给了action里面的域。好比:

<from action="baidu.com">
    // you form filed
</from>

上面这个表单提交后,剩余的操作就交给了action里面的域baidu.com,本页面的逻辑和这个表单没啥关系,由于不关系请求的响应,所以浏览器认为是安全的。因为表单提交是一个提交数据的过程,表单提交后就会跳转,表单所在的页面是没办法获取结果的。抽象一点说,form提交和点击a标签跳转差不多,没必要对这个做限制。

而使用ajax来控制form的请求的时候,页面js会需要知道请求的返回值,这个时候,浏览器发出跨域请求,需要获得授权才可以成功请求,否则是会拒绝的。如果Ajax表单跨域提交想得到响应结果,目标服务器应该设置Access-Control-Allow-Origin。

9种跨域解决方法

例子中可运行源码

1,JSONP跨域

jsonp的原理就是利用

jsonp有点: JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题 。

jsonp缺点: 仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
1)原生JS实现:

<script>
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);

    // 回调执行函数,后端返回的时候会被执行到
    function handleCallback(res) {
        alert(JSON.stringify(res));
    }
 </script>

服务端返回如下(返回时即执行全局函数):

handleCallback({"success": true, "user": "admin"})

2)jquery Ajax实现:

$.ajax({
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",  // 自定义回调函数名
    data: {}
});

3)Vue axios实现:

this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

后端node.js代码:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res) {
    var params = querystring.parse(req.url.split('?')[1]);
    var fn = params.callback;

    // jsonp返回设置
    res.writeHead(200, { 'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');

    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

2,nginx代理跨域

通过配置文件设置 请求响应头 Access-Control-Allow-Origin…等字段。

1)nginx配置解决 icon font 跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}

2)nginx反向代理接口跨域(不需要前端做什么)Nginx配置学习

server {    
    listen    8080;    
    server_name localhost;         
    location /default/ {      
      proxy_pass http://localhost;    
    } 
}

以上的配置中,
listen 表示nginx要监听的端口;

server_name 就是访问nginx时在浏览器中输入的域名,可以直接填ip地址,要绑定多个可以用空格隔开;

location 表示nginx监听该端口时要匹配的url,如果访问nginx的url中包含有/default/就执行代理

proxy_pass 表示nginx要把客户端的请求代理到的目标。

比如:www.github.cn -> www.github.com

#proxy服务器
server {
    listen       80;
    server_name  www.github.com;
    location / {
        proxy_pass   www.github.cn; #反向代理
        proxy_cookie_demo www.github.cn www.github.com;
        add_header Access-Control-Allow-Origin www.github.cn;
        add_header Access-Control-Allow-Credentials true;
    }
}

3,WebSocket协议跨域

WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。

WebSocket和HTTP都是应用层协议,都基于 TCP 协议。

但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。

同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

前端处理:

// socket.html
<script>
  let socket = new WebSocket('ws://localhost:3000');
  socket.onopen = function () {
    socket.send('heiheihei');//向服务器发送数据
  }
  socket.onmessage = function (e) {
    console.log(e.data);//接收服务器返回的数据
  }
</script>

服务器端处理:

// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//记得安装ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('嘿嘿嘿')
  });
})

4,node中间件代理

实现原理:同源策略是浏览器需要遵循的标准,而如果是服务器向服务器请求就无需遵循同源策略。 代理服务器,需要做以下几个步骤:

  1. 接受客户端请求 。
  2. 将请求 转发给服务器。
  3. 拿到服务器 响应 数据。
  4. 将 响应 转发给客户端。
    跨域原理和解决方案_第1张图片

5,跨域资源共享(CORS)

CORS 需要浏览器和后端同时支持。IE 8 和 9 需要通过 XDomainRequest 来实现。浏览器会自动进行 CORS 通信,实现 CORS 通信的关键是后端。只要后端实现了 CORS,就实现了跨域。

服务端设置 Access-Control-Allow-Origin 就可以开启 CORS。 该属性表示哪些域名可以访问资源,如果设置通配符则表示所有网站都可以访问资源。
虽然设置 CORS 和前端没什么关系,但是通过这种方式解决跨域问题的话,会在发送请求时出现两种情况,分别为简单请求和复杂请求。

1) 简单请求
只要同时满足以下两大条件,就属于简单请求
条件1:使用下列方法之一:

  • GET
  • HEAD
  • POST

条件2:Content-Type 的值仅限于下列三者之一:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器; XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问。

2) 复杂请求
不符合以上条件的请求就肯定是复杂请求了。 复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,该请求是 option 方法的,通过该请求来知道服务端是否允许跨域请求。

// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie不能跨域
xhr.withCredentials = true // 前端设置是否带cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //得到响应头,后台需设置Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
  }
}
xhr.send()

6,基于post message实现跨域处理

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:

a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。

7,用 document.domain + iframe实现跨域

只能实现同一个主域,不同子域之间的操作.
v.qq.comsports.qq.com是在同一个主域。

父页面Ahttp://www.zhufengpeixun.cn/A.html

<iframe src="http://school.zhufengpeixun.cn/B.html"></iframe>
<script>
	document.domain = 'zhufengpeixun.cn';
	var user = 'admin';
</script>

子页面Bhttp://school.zhufengpeixun.cn/B.html

<script>
	document.domain = 'zhufengpeixun.cn';
	alert(window.parent.user);
</script>

8,location.hash + iframe跨域

9,window.name + iframe跨域

参考
参考
参考

你可能感兴趣的:(计算机网络,跨域原理,跨域解决方案)