前端跨域方案全家桶

248 阅读6分钟

定义

所谓跨域,就是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。而我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。
有关同源策略,可以阅读阮一峰老师的文章,讲的非常详细:
www.ruanyifeng.com/blog/2016/0…

总结的来说,由于浏览器安全限制,数据是不可以直接跨域请求的,包括不同的根域名、二级域名、或不同的端口,除非目标域名授权你可以访问。想要解决同源策略实现传输,就需要跨域。
网站url由 协议 + 域名 + 端口 + 路径组成,也可能包含查询参数和锚点
这里我借用了方应航老师画的图:

只要两个网址的协议、域名、端口、有一个不一样,这两个网址就是非同源,要发送请求就需要跨域。
接下来我们就盘点一下跨域的解决方案:
1、 jsonp
2、跨域资源共享(CORS)
3、postMessage
4、window.name + iframe
5、location.hash + iframe
6、 document.domain + iframe
7、 WebSocket协议跨域
8、 nginx代理跨域

1、JSONP

原理

虽然有同源策略的限制,但script却可以随意引用外部的链接。JSONP就是利用了这一特性,通过创建一个script标签,其src指向非同源的url,来实现跨域。

方法

1、写一个jsonp函数,返回一个promise对象
2、创建一个script标签
2、script.src = url
3、在document中插入script标签
4、在全局挂载一个show函数,执行resolve,并且删除script标签

原生代码实现:

    function jsonp(url){
      return new Promise((resolve,reject)=>{
        let script = document.createElement('script')
        script.src = url
        document.body.appendChild(script)
        window.show = function(data){
          resolve(data)
          document.body.removeChild(script)
        }
      })
    } 
    jsonp(url).then((data)=>{
      console.log(data)
    })

不过JSONP也有比较明显的缺点,只能发送GET请求,而且比较缺乏安全性,容易收到xss攻击。

2、CORS(跨域资源共享)

这种方法也是比较常用的一种。CORS全称是Cross-Origin Resource Sharing,也就是跨域资源共享。
通俗一点来讲,就是如果A客户端想要跨域去给不同域名的B服务器发送请求,就提前和B服务器说好,然后B的后端代码就加上一段允许A发送请求并响应A的代码,达到效果。
加入的后端代码如下:(用到了express)

let express = require('express')
let app = express()
let whiteLists = ['//你同意请求的页面']
app.use(function(req,res)=>{
    let origin =req.headers.origin
    if(whiteLists.includes(origin)){
        res.setHeader('Access-Control-Allow-Origin', origin)
    }
})

res.setHeader('Access-Control-Allow-Origin', '//你同意请求并响应的页面域名')加入这句就表明这个网站加入白名单,可以响应他的请求。
要注意,默认支持的请求方式是get、post、head,如果要使用put这类复杂请求,同样需要在后端setheader

res.setHeader('Access-Control-Allow-Methods','put')

常见的setHeader总结:

res.setHeader('Access-Control-Allow-Origin',origin)
//允许哪个地址向我发送请求
res.SetHeader('Access-Control-Allow-Headers','name')
//允许哪个请求头访问我
res.setHeader('Access-Control-Aloow-Methods','PUT')
//允许哪个方法访问我
res.setHeader('Access-Control-Allow-Credentials',ture)
//允许携带cookie
res.setHeader('Access-COntrol-Max-Age',1)
//允许存活的时间(单位时间为5s)
res.setHeader('Access-Control-Expose-Headers','name')
//允许返回的头

3、postMessage

postMessage是HTML5中的一个api,可以实现跨域操作的window属性,它可以解决以下问题:

  • 页面与其新打开的窗口的数据传递
  • 多窗口之间的数据传递
  • 页面与嵌套的iframe消息传递

总的来说,postMessage()可以实现跨文本档、多窗口、跨域消息传递。

具体步骤
1、在html中写一个iframe标签,src属性为需要通信的窗口的url
2、在iframe标签设置一个onload监听事件函数load
3、load函数中对iframe的contentWindow使用postMessage('内容','url')
4、在接收的网页中写window.onMessage = function(e){console.log(e.data)}接收发送内容
5、如果需要返回内容,则继续在onMessage中加上e.source.postMessaqg('内容',e.origin)

实现代码
http://localhost:8080/x.html页面向http://localhost:8081/y.html传递'request',然后后者传回'response'。

x.html(http://localhost:8080/x.html)

  <iframe src="http://localhost:8081/y.html" frameborder="0" id="frame" onload="load()"></iframe> //等它加载完触发一个事件
  //内嵌在http://localhost:8080/x.html
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('request', 'http://localhost:8081') //发送数据
        window.onmessage = function(e) { //接受返回数据
          console.log(e.data) //'response'
        }
      }
    </script>

y.html(http://localhost:8081/y.html)

  window.onmessage = function(e) {
    console.log(e.data) //'request'
    e.source.postMessage('response', e.origin)
 }

4、window.name

window.name的特性:name值在加载完其他页面后仍然能够存在,可支持2MB。我们可以利用这一特性实现跨域。

实现步骤
1、创建一个iframe标签,src属性为需要发送内容的url
2、在iframe上设置一个onload监听事件函数load
3、设置onload事件触发两次,第一次在load函数中修改iframe的src,改为同域下的另外一个网页,可为空白页,并把数据保存在window.name
4、第二次触发onload事件,读取同域window.name中的数据

实现代码
x.html与y.html同域,向z.html发送内容。其中b.html为中间代理页,与a.html同域,内容为空。

 <iframe src="http://localhost:8080/z.html" frameborder="0" id="iframe" onload="load()"></iframe>
 <script>
   let first = true
   function load(){
     if(first){
       let iframe = docuement.getElementById('iframe')       
       iframe.src = 'http://localhost:8080/y.html'
       fitst = false
       }else{
         console.log(iframe.contentWindow.name)
     }
     
   }
 </script>

5、location.hash + iframe

思路:x给z传一个hash值,z收到hash值后,把hash值传递给y,y将结果放到x的hash中。xyz三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

实现代码
x.html

  <iframe src="http://localhost.8081/z.html#text1" frameborder="0"></iframe>
 <script>
    window.onhashchange = function(){
      console.log(location.hash)
    } 
 </script>

z.html

  <script>
      console.log(location.hash)
      let value - location.hash
      let iframe = document.createElement('iframe')
      iframe.src = `http://localhost:8080/y.html#Ireceived${value}`
      document.body.appendChild(iframe)
  </script>

y.html

  <script>
      window.parent.parent.location.hash = location.hash
  </script>

6、document.domain + iframe

适用场景:二级域名相同的两个网页进行发送请求,如a.test.comb.test.com

实现原理:在两个页面中的js加入document.domain ='test.com',也就是document.domain = '相同的二级域名'

实现代码: a.html

 The a.html
 <iframe src="http://b.test.com:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
 <script>
   document.domain = 'test.com'
   function load() {
     console.log(frame.contentWindow.a);
   }
 </script>

b.html

   The b.html
  <script>
    document.domain = 'test.com'
    let a = 100;
  </script>

这样a就可以获取b的变量a了

7、websocket

Websocket是一个现成的api,想要使用的话,直接new就可以了

let socket = new Websocket()

但是这个api兼容性差,所以我们一般使用封装零零websocket接口的socket.io

这里展示原生webocket的实现代码
客户端:

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

服务端:

let express = require('express');
let app = express();
let WebSocket = require('ws');//需要安装ws
let wss = new WebSocket.Server({port:8080});
wss.on('connection',function(ws) {
  ws.on('message', function (e) {
    console.log(e.data);
    ws.send('response')
  });
})

8、 nginx代理跨域

Nginx是一款轻量级的Web服务器、反向代理服务器,我们可以搭建一个nginx服务器用于中转,从而实现转发请求。

实现思路
1、在nginx配置一个代理服务器作为跳板
2、反向代理访问domain2接口,实现跨域

实现步骤
1、在官网下载nginx
2、在nginx目录下修改nignx.conf
3、启动nginx

修改nignx.conf的代码如下:

server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
    }
}