前端如何解决跨域问题

179 阅读4分钟

一、跨域是什么?

同源策略

跨域问题其实就是浏览器的同源策略所导致的。

什么是同源

protocol(协议)、domain(域名)、port(端口)三者一致。除此之外,都是跨域。

www.example.com:80/a.js

www.example.com:80/b.js

二、如何解决跨域?

  1. CORS

跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的 Web 应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器「不同的域、协议或端口」请求一个资源时,资源会发起一个「跨域 HTTP 请求」。

服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

//指定允许其他域名访问
'Access-Control-Allow-Origin:*'//或指定域
//响应类型
'Access-Control-Allow-Methods:GET,POST'
//响应头设置
'Access-Control-Allow-Headers:x-requested-with,content-type'

想要传递 cookie 需要满足 3 个条件:

1.web 请求设置withCredentials

这里默认情况下在跨域请求,浏览器是不带 cookie 的。但是我们可以通过设置 withCredentials 来进行传递 cookie.

// 原生 xml 的设置方式
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
// axios 设置方式
axios.defaults.withCredentials = true;

2.Access-Control-Allow-Credentials 为 true

3.Access-Control-Allow-Origin为非 *

2.Node 正向代理

服务器与服务器之间请求数据并不会存在跨域行为,跨域行为是浏览器安全策略限制

3.Nginx 反向代理

  • 使用charles等正向代理方式比较简单,需要掌握的知识点也比较少。但相应的其可配置性较弱,仅适合中小型项目使用。
  • 使用nginx的反向代理则相对复杂一些,需要了解基本的nginx配置。但其可配置性较强,支持URL的正则匹配,设置优先级等,适合复杂的项目使用。

4.JSONP

JSONP 主要就是利用了 script 标签没有跨域限制的这个特性来完成的。

  • 使用限制

    仅支持 GET 方法,如果想使用完整的 REST 接口,请使用 CORS 或者其他代理方式。

  • 流程解析

    1.前端定义解析函数(例如 jsonpCallback=function(){....})

    2.通过 params 形式包装请求参数,并且声明执行函数(例如 cb=jsonpCallback)

    3.后端获取前端声明的执行函数(jsonpCallback),并以带上参数并调用执行函数的方式传递给前端。

<script type="text/javascript">
    window.jsonpCallback = function(res) {
        console.log(res);
    };
</script>
<script
    src="http://localhost:8080/api/jsonp?msg=hello&cb=jsonpCallback"
    type="text/javascript"
>
</script>

5.Websocket

WebSocket 规范定义了一种 API,可在网络浏览器和服务器之间建立“套接字”连接。简单地说:客户端和服务器之间存在持久的连接,而且双方都可以随时开始发送数据。

6.window.postMessage

  • 页面和其打开的新窗口的数据传递

  • 多窗口之间消息传递

  • 页面与嵌套的 iframe 消息传递

7.document.domain + Iframe

「该方式只能用于二级域名相同的情况下,比如 a.test.com 和 b.test.com 适用于该方式」。 只需要给页面添加 document.domain ='test.com' 表示二级域名都相同就可以实现跨域。

www.   baidu.  com     .
三级域  二级域   顶级域   根域
// a.test.com
<body>  
   helloa  
   <iframe
     src="http://b.test.com/b.html"
     frameborder="0"
     onload="load()"
     id="frame"
   ></iframe>  
   <script>    
       document.domain = "test.com";    
       function load() {     
           console.log(frame.contentWindow.a);    
       }  
   </script>
</body>
// b.test.com
<body>
  hellob
  <script>
    document.domain = "test.com";
    var a = 100;
  </script>
</body>

8.window.location.hash + Iframe

  • 实现原理

原理就是通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。

  • 实现流程

一开始 a.html 给 c.html 传一个 hash 值,然后 c.html 收到 hash 值后,再把 hash 值传递给 b.html,最后 b.html 将结果放到 a.html 的 hash 值中。 同样的,a.html 和 b.htm l 是同域的,都是 http://localhost:8000,而 c.html 是http://localhost:8080

// a.html
<iframe src="http://localhost:8080/hash/c.html#name1"></iframe>
<script>  
    console.log(location.hash);  
    window.onhashchange = function() {    
      console.log(location.hash);  
    };
</script>
// b.html
<script>  
    window.parent.parent.location.hash = location.hash;
</script>
// c.html
<body></body>
<script>  
    console.log(location.hash);  
    const iframe = document.createElement("iframe");  
    iframe.src = "http://localhost:8000/hash/b.html#name2";  
    document.body.appendChild(iframe);
</script>

9.window.name + Iframe

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变。

其中 a.html 和 b.html 是同域的,都是http://localhost:8000,而 c.html 是http://localhost:8080

// a.html
<iframe  
    src="http://localhost:8080/name/c.html"  
    frameborder="0"  
    onload="load()"  
    id="iframe">
</iframe>
<script>  
    let first = true;  // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name  
    function load() {    
        if (first) { // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.src = "http://localhost:8000/name/b.html";
            first = false;
        } else { // 第2次onload(同域b.html页)成功后,读取同域window.name中数据
            console.log(iframe.contentWindow.name);    
        }  
    }
</script>

b.html 为中间代理页,与 a.html 同域,内容为空。

// b.html
<div></div>
// c.html
<script>  
    window.name = "秋风的笔记";
</script>

通过 iframe 的 src 属性由外域转向本地域,跨域数据即由 iframe 的 window.name 从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

参考文章:

10种跨域解决方案

前端开发如何独立解决跨域问题