# 跨域问题

跨域问题属于浏览器的同域限制,禁止不同域名进行通信,以免产生不可控制的安全问题。

由于跨域限制是浏览器的限制,命令行curl就没有跨域限制

跨域的请求可以发送,服务端也能够接收到请求,返回的响应报文也能够接收的到,但是浏览器解析报文的时候发现是跨域的,就会把返回的信息忽略,并在命令行报错

# 什么是浏览器同源策略及限制?

不符合浏览器同源策略就会引起跨域限制

同源协议域名端口完全相同,有一个不同就不同源。

同源策略:限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制。

跨域限制

  • Cookie、LocalStorage、IndexDB无法读取
  • DOM无法获取
  • Ajax请求不能发送

# 如何进行跨域通信呢?

避开浏览器的同源策略进行跨域通信

# JSONP

利用浏览器对script标签引用资源没有同源策略限制的特性。

特点

  • 只支持GET请求
  • 需要服务端配合实现
  • 兼容性好,各版本浏览器都支持
  • 有安全隐患,容易被人利用对服务进行访问

简易实现思路

/**
 * 利用script的src属性发出跨域的get请求
 * 
 * 服务端接受到请求后
 * 把数据通过callback参数指定的函数进行返回
 * 返回内容:<script>setUserInfo(json)</script>
 **/
<script src="http://xxx.xxx/123?callback=setUserInfo"></script>

// callback函数实现,服务端返回后会命中
<script type="text/javascript">
  function setUserInfo(json) {
    // 拿到数据,进行操作    
  }
</script>

# location.hash + iframe

location.hash + iframe 可实现两个不同域名页面间的通信

location.hashurl#后面的东西,通过onhashchange (opens new window)可以监听hash变动,hash变动页面不会刷新

特点

  • 通过页面进行通信,页面在自己域名下通信可以实现不同http方法的请求
  • 有安全隐患,容易被人恶意嵌套iframe进行hash修改
  • 兼容性好,各版本浏览器都支持
  • 一般不会使用此方法

简易实现思路

// parent.html

// ...
<iframe src="http://xxxxx/child.html#goHome"></iframe>
// ...
<script>
  // 监听hash变化
  window.addEventListener("hashchange", () => {
    console.log(location.hash); // ok
  }, false);
</script>


 // child.html
<script>
  window.addEventListener("hashchange", () => {
    console.log(location.hash); // goHome
    window.parent.parent.location.hash = ok;
  }, false);
</script>

# postMessage

h5中新增的跨域通信 window.postMessage()MDN介绍 (opens new window)

特点

  • 可以同域名,不同窗口间进行通信
  • 可以在当前页面与iframe间进行通信
  • 相对安全,可以通过origin和source来验证身份
  • 不支持ie11及以下版本

简易实现思路

// http://parent.com
var popup = window.open('http://child.com');

popup.postMessage("hello child", "http://child.com");

function receiveMessage(event)
{
  // 验证身份
  if (event.origin !== "http://child.com") return;
}

window.addEventListener("message", receiveMessage, false);


// http://child.com
function receiveMessage(event)
{
  // 我们能信任信息来源吗?
  if (event.origin !== "http://parent.com") return;

  event.source.postMessage("hi, parent", event.origin);
}

window.addEventListener("message", receiveMessage, false);

# WebSocket

使用ws / wss协议(加密 / 非加密),基于TCP协议。服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息。

WebSocketMDN介绍 (opens new window)

特点

  • 建立在TCP协议之上
  • 性能开销小,通信高效
  • 可发送文本,也可发送二进制数据
  • 需要服务端配合
  • ie10以下不支持
  • 使用wss 安全性有保证

简易实现思路

// 客户端实现
let ws = new WebSocket('ws://test.com');
ws.onopen = function () {
  console.log('connected');
  ws.send('123'); // 向服务端推送数据
}

ws.onmessage = function (e) {
  console.log('receiveMessage');
  console.log(e.data);
  
  // 关闭连接,可以自己找合适时机,正常不会接受一次消息就关闭
  ws.close();
}

ws.onclose = function(evt) {
  console.log("disconnected");
}


// 服务端实现,感兴趣的可以自行查找

# CORS

CORSCross-origin resource sharing),整个CORS通信过程,都是浏览器自动进行的,不需要客户端做任何配置(本质还是XMLHttpRequest)。但需要服务端进行一些配置才能实现。 CORSMDN介绍 (opens new window)

特点

  • 不需要客户端做任何操作
  • 需要服务端配合
  • ie10以下不支持
  • 可指定跨域范围,比较安全
  • 比较常用

实现方法:

服务端设置HTTP响应头

Access-Control-Allow-Origin

跨域请求时,服务端允许哪些域名访问。 浏览器解析到这个头获取值,与当前域名对比。如果能匹配上,就把服务端返回的值返回。

例:Access-Control-Allow-Origin: https://test.com(服务端允许https://test.com的跨域访问)

Access-Control-Allow-Credentials

跨域请求时,服务端允不允许浏览器携带cookie。 true:允许 false:不允许

例:Access-Control-Allow-Credentials: true(服务端允许携带cookie)

Access-Control-Expose-Headers

在跨源访问时,只能拿到一些最基本的响应头(Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragmaAccess-Control-Expose-Headers让服务器把允许浏览器访问的头放入白名单

# nginx 反向代理

通过部署nginx服务,让客户端访问同域名的地址,通过nginx代理,把地址代理到其它域名的服务地址上,实现跨域访问

特点

  • 不需要客户端做任何操作
  • 不需要服务端配合
  • 兼容所有浏览器
  • 自己设置代理,比较安全
  • 比较常用

# SSE

Server-Sent events服务器发送事件,和WebSocket类似,但是单向通信,只能服务器向浏览器发送,以stream传输数据。

特点

  • 单向通信
  • 轻量级,使用简单
  • stream传输数据
  • 基于HTTP协议
  • 支持断线重连
  • 比较安全
  • 不支持ie11及以下

服务端向客户端声明,接下来要发送流信息,不是一次性数据包,而是一个数据流,会不断发送过来。类似视频播放

简易实现思路

// 客户端实现
let source = new EventSource('htp://test.com');
source.onopen = function () {
  console.log('connected');
}

source.onmessage = function (e) {
  console.log('receiveMessage');
  console.log(e.data);
  
  // 关闭连接,可以自己找合适时机,正常不会接受一次消息就关闭
  source.close();
}

source.onerror = function (e) {
  // 发生错误,或中断触发,可以在这进行重连
}


// 服务端实现,感兴趣的可以自行查找