同源和跨域的那些事-简单图文总结-LMLPHP

01、为什么要跨域?

跨域的根本原因是浏览器的“同源策略”,得先了解什么是同源?—— 就是【协议+域名+端口号】相同,即为同源,只能向同源的服务发起AJAX请求。

同源和跨域的那些事-简单图文总结-LMLPHP

❓为什么要同源呢?

这是浏览器故意设计的,是浏览器的基本安全策略,否则会很容易受到XSS、CSRF攻击。只能向同源的服务发起AJAX请求,不可跨域请求,会被浏览器拦截。

❓有哪些限制规则呢?

  • ✅ 访问其他源的图片、CSS、JS是可以的,允许<img src="url"><link href="url"><script src="url">元素获取的其他源的资源。
  • ✅ Form表单可以跨域提交,表单的提交只是提交数据无需返回,浏览器认为是安全的。
  • 🚫 AJAX不可以向其他源发送网络请求,会被浏览器拦截。注意拦截的不是请求,而是响应,服务端依然是可以收到请求的。
  • 🚫 仅可访问自己域的cookie、localStorage、DOM树,不能访问Iframe嵌入的其他页面内部内容。

同源和跨域的那些事-简单图文总结-LMLPHP


02、如何实现跨域?

随着互联网越来越复杂,需求也越来越多,跨域请求就很常见了。我们知道了跨域是浏览器的同源限制,就可以针对性的想办法了。

2.1、JSONP跨域

这是一种传统的跨域请求办法,借助于<script>标签元素,因为<script>src可以访问任何站点的资源。当然这需要服务端对应接口支持JSONP(JSON with padding)协议,所以是需要双方约定好,所以浏览器认为这是安全的。

  • 优点是兼任IE,实现跨域。
  • 缺点是不能控制请求过程,仅支持GET方式请求。因为只是一个<script>标签,浏览器自动发起的资源请求。

📢前端具体实现过程:

  • 1、申明一个全局的回调函数“getData”来接收数据。
  • 2、动态创建一个<script>标签,src为要跨域的API地址,URL中带上回调参数“callback=getData”。
  • 3、服务端收到请求后,动态生成一个脚本,脚本内容是一个字符串,由回调+返回的数据构成:“getData('data')”。
  • 4、本地执行远程脚本,回调函数“getData”运行,就得到了想要的数据。
<script src="http:www.thrid.com/cors/api?q=key&callback=back"></script>
<script>
  function back(data) {
    console.log(data);
  }
</script>

JSONP的实现:

function jsonp(url, args, cbName) {
  return new Promise((resolve, reject) => {
    const ele = document.createElement('script');
    window[cbName] = (data) => {
      resolve(data);
      document.body.removeChild(ele);
    }
    args = { ...args, callback: cbName };
    ele.src = `${url}?${Object.keys(args).map(k => `${k}=${args[k]}`).join('&')}`;
    document.body.appendChild(ele);
  });
}
//使用,api为360的公开接口
jsonp('https://sug.so.360.cn/suggest', { format: 'jsonp', word: 'china' }, 'search')
  .then(function (data) {
    console.log(data)
  });

2.2、CORS跨域

CORS是什么?—— 跨域资源共享 (cross-origin resource sharing),让AJAX可以跨域访问数据。这是为了满足跨域请求的需求,W3C新增加的特性,需要服务端的支持,不支持IE8/9。根据请求方式,浏览器将CORS分为两种情况:

  • 简单请求(安全请求):只支持GET、POST、HEAD,Header只支持部分字段。
  • 复杂请求(其他请求):简单请求以外的其他跨域请求。

🔵简单请求

基本原理就是在请求头加入一个身份来源标识,服务端根据这个标识来判等是否允许访问,如果允许则给一个允许的标记并返回响应。

  • 只支持GET、POST、HEAD。
  • header —— 我们仅能设置基础的安全字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type 的值为 application/x-www-form-urlencoded,multipart/form-data 或 text/plain。

📢具体过程比较简单,前端只要在Header加入“Origin”即可:

  • 请求头Header加入要跨域的源:origin:http://www.main.com
GET /api HTTP/1.1
Origin: http://www.main.com				//本次请求来自哪个源
Host: http://www.third.com				//请求的第三方API
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0
...
  • 服务端收到请求后检查Origin,如果同意请求则正常响应,同时在响应的Header中加入特殊的“Access-Control-Allow-Origin”字段,申明支持的源,也可以用“*”表示支持任何源访问。
  • 浏览器收到响应后会检查“Access-Control-Allow-Origin”,和当前源对比,如果不合法则会报错——跨域。
Access-Control-Allow-Origin: http://www.main.com		//请求允许的源
Access-Control-Allow-Credentials: true							//是否允许cookie,cors默认不发送cookie,如果要发送,还需AJAX中设置withCredentials
Access-Control-Expose-Headers: Content-Length,API-Key	//如果客户端想要访问其他非安全字段,则需要服务端明确定义哪些Header字段暴露出来
Content-Type: text/html; charset=utf-8

同源和跨域的那些事-简单图文总结-LMLPHP

🟠复杂请求

不是简单请求的都称为复杂请求(非简单请求),如请求方法是PUT、DELETE,或Content-Type=application/json。相比于简单请求,复杂请求多了一次预请求。

预请求

  • 正式发送请求前,浏览器会自动发送一个预请求,问问服务端是否允许本次请求,如果回应允许才正式发送请求,后面就和简单请求相同了。
  • 预请求及其响应都没有body,采用OPTIONS方法。

同源和跨域的那些事-简单图文总结-LMLPHP


03、跨域小结

因为同源是浏览器的限制,跨域的方法无非就是绕过,或采用CORS。


参考资料


06-22 00:02