前端常见的跨域解决方案(附源码)

同源策略

同源策源是一种约定,这也是出现跨域的原因,它是浏览器最基本的安全功能,浏览器的的同源策源,限制了来自不同源的document脚本,对当前document读写或设置某些属性。这一策略非常重要,如果没有这个策略,可能a.com的一段javascript脚本,在b.com的页面未曾加载此脚本时,也可以随意更改b.com的页面,于是浏览器提出了“origin”源的概念,来自不同源的对象无法互相干扰。
对于javascript来说,不同源存在三种情况:不同协议不同域不同端口号,这三种情况全部满足,才算同源。
然而往往我们难免需要跨域请求一些资源,所以就有了以下对策

  • jsonp
  • document.domain+iframe
  • localtion.hash+iframe
  • window.name+iframe
  • postMessage
  • cors
  • WebSocket
  • nginx

那么接下来,咱们就讲一下以上方案的具体细节和它的优缺点。

解决方案

jsonp

这里模拟跨域,是直接在iis布置了两个端口不同的站点。

原理

就是动态创建一个script标签,利用src的属性,传入一个函数,服务端检测,调用这个函数,把结果传回去。

//x.js
var xxx=function(data){
    console.log(data)
}
//创建一个script标签
var script=document.createElement("script");
//传入一个函数,接受返回值
script.src="http://192.168.1.103:6789/y.js?callback=xxx";
document.body.appendChild(script)  
//y.js
//检测传过来的值,把结果返回,这里我是用脚本模拟,服务端和这里原理是一样的,当前检测要比这复杂的多,因为这会牵扯安全问题。
typeof(xxx)=="function"&&xxx({"msg":"hello world"});

结果

在这里插入图片描述

缺点

因为用到是script标签,所以只能get请求,另外可能存在安全问题。

document.domain+iframe

这里模拟跨域,我是配置了hosts,两个主域相同,子域不同的网址。
host文件
127.0.0.1 x.localhost.com
127.0.0.1 y.localhost.com
然后利用iis配置了两个站点,分别是以ip为127.0.0.1,主机名为x.localhost.com,y.localhost.com

原理

就是利用iframe,然后分别设置相同的主域。

//X.html
    <iframe src="http://y.localhost.com:8000/y.html" id="iframe"></iframe>
    <script>
        document.domain="localhost.com"
        iframe.onload=function(){
            const iframe=document.getElementById('iframe')
            const doc=iframe.contentWindow.document; 
            console.log(doc.getElementsByTagName("h1")[0].innerHTML)  
        }
    </script>
//y.html
	<h1>xxxxxxx</h1>
    <script>
        document.domain="localhost.com"
    </script>

结果

在这里插入图片描述

缺点

必须主域相同。

localhost.hash+iframe

这里同上一配置一样

原理

就是在跨域页面,跳转到需要跨域请求的页面,然后利用hash传值。

//x.html
   <iframe src="http://y.localhost.com:8000/y.html" id="iframe"></iframe>
    <script>
        const iframe=document.getElementById("iframe")
        iframe.onload=function(){
            console.log(location.hash)//#msg=hello
        }
    </script>
//y.html
    <script>
        const msg="hello"
        parent.location.href=`http://x.localhost.com:8000/x.html#msg=${msg}`
    </script>

结果

在这里插入图片描述

缺点

以上图片很明显可以看出,利用hash,结果是在url上的,而且有长度限制,还要自己解析,非常麻烦,另外就是安全问题。
ps:针对安全问题,随后我会再写个博客,具体讲下,有兴趣的朋友可以关注下,嘿嘿。

window.name+iframe

这里同上一配置一样

原理

就是利用name 值在不同的页面(甚至不同域名)加载后依旧存在,并且可以支持非常长的 name 值(大约2MB)的特性,来跨域传值。

//x.html 实际加载两次
    <iframe src="http://y.localhost.com:8000/y.html" id="iframe"></iframe>
    <script>
        const iframe=document.getElementById("iframe")
        iframe.onload=function(){
            console.log(iframe.contentWindow.name)//hello
        }
    </script>
//y.html
  <script>
        const msg="hello"
        window.name=msg;
        //这里注意,跳转到x.html,再创建个iframe,然后就创造出两个同域下window.name相同的情景了。
        location.href = 'http://x.localhost.com:8000/x.html'
    </script>

缺点

感觉比较麻烦,而且改window.name不太好吧。

postMessage

这里我重新配置了hosts,不同域名,不同端口,为了测验是否有这方便的限制

原理

window.postMessage()是h5的一个新API,用来实现跨源通信,window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后向目标窗口派发一个 MessageEvent 消息,这样就可以用addEventListener()来监听派遣message了。

otherWindow.postMessage(message, targetOrigin, [transfer])

以上可以看出postMessage有三个参数,而最后一个可选。

  • otherWindow:接受消息的窗口(window)。

  • message:需要发送的消息。

  • targetOrigin:如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到哪些窗口;例如,当用postMessage传送密码时,这个参数就显得尤为重要,必须保证它的值与这条包含密码的信息的预期接受者的origin属性完全一致,来防止密码被恶意的第三方截获。如果你明确的知道消息应该发送到哪个窗口,那么请始终提供一个有确切值的targetOrigin,而不是*。不提供确切的目标将导致数据泄露到任何对数据感兴趣的恶意站点。

transfer:是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。

下面就看一个例子,这个例子详细列出来两个不同源的页面之间的相互通信。

//x.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <iframe src="http://y.localhost.org:6789/y.html" id="iframe"></iframe>
    <script>
       const iframe=document.getElementById("iframe")
       iframe.onload=function(){
       	 //向y.html这个窗口发送消息
         iframe.contentWindow.postMessage("我来啦","http://y.localhost.org:6789")
       }
       //监听y.html窗口发来的消息
        window.addEventListener("message", function(e){
            console.log(e.data)//{msg: "helloword"}
        }, false);
    </script>
</body>

</html>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
   <h1>xxxxxxx</h1>
    <script>
    	 //监听x.html窗口发来的消息
          window.addEventListener("message",function(event){
          	//并页面h1把值换成传来的数据
            document.querySelector("h1").innerText=e.data;
            //给y.html回应消息
            parent.postMessage({"msg":"helloword"},event.origin);
          })
    </script>
</body>
</html>

结果

在这里插入图片描述

缺点

CORS

模拟配置:利用node-express开了一个与前端站点不同域/不同端口的服务。

原理

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

CORS分两种请求,一种简单请求,一种非简单请求。
简单请求

  • 请求方法只能是HEAD,GET,POST之一
  • HTTP的头信息不超出以下几种字段
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:只限于三个值
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain
//x.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
        <script>
            const xhr=new XMLHttpRequest()
            xhr.open("get","http://y.localhost.com:4567/y",true)
            xhr.onreadystatechange=function(){
                if (xhr.readyState == 4 && xhr.status == 200) {
                    console.log(xhr.responseText)//hello;
                }
            }
            xhr.send(null)
        </script>
</body>

</html>
//简单的利用express开一个服务
const express=require("express")

const app=express()

app.get("/y",(req,res)=>{
  	//返回hello
    res.end("hello")
})
//开启服务
app.listen("4567",()=>{
    console.log("监听成功.....")
})

以上代码就属于简单请求,那么浏览器发现ajax是简单请求时,会自动在请求头中添加一个origin字段
在这里插入图片描述
这里origin字段是用来表示来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
如果服务器发现不再许可范围内,会返回一个正常的HTTP回应。浏览器发现,这个回应的头中没有包含Access-Control-Allow-Origin字段,这时候就会报一个跨域的错误。
在这里插入图片描述
在这里插入图片描述
这时候,咱们只需在服务器,配置Access-Control-Allow-Origin即可,指定允许访问的源。

const express=require("express")

const app=express()

app.get("/y",(req,res)=>{
    res.header("Access-Control-Allow-Origin", "*");
    res.end("hello")
})

app.listen("4567",()=>{
    console.log("监听成功.....")
})

这时候,再看响应头,就新增了Access-Control-Allow-Origin,并指定了允许访问的源,这时候就可以跨域访问了。(PS:我这里设置的是*,表示全部,大家也是可以指定具体源)。
在这里插入图片描述
这里还需要注意一点:
CORS默认是不发送cookie,如果想要发送cookie,这时候就需要前后端配合了。
服务端:添加一个Access-Control-Allow-Credentials字段,值为true,代表允许客户端发送cookie,另外这时候,Access-Control-Allow-Origin就不能设置为*号了,需要改为指定的具体源;

const express=require("express")

const app=express()

app.get("/y",(req,res)=>{
    res.header("Access-Control-Allow-Origin", "http://x.localhost.com:8000");
    res.header('Access-Control-Allow-Credentials','true'), // 后端允许发送Cookie)
    res.end("hello")
})

app.listen("4567",()=>{
    console.log("监听成功.....")
})

客户端:ajax打开withCredentials属性,设为true

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
        <script>
            const xhr=new XMLHttpRequest()
            xhr.open("get","http://y.localhost.com:4567/y",true)
            xhr.withCredentials = true;
            xhr.onreadystatechange=function(){
                if (xhr.readyState == 4 && xhr.status == 200) {
                    console.log(xhr.responseText)//hello;
                }
            }
            xhr.send(null)
        </script>
</body>

</html>

非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
这里主要是服务端设置一系列响应头

const express=require("express")

const app=express()

app.get("/y",(req,res)=>{
    res.header("Access-Control-Allow-Origin", "*");
    res.header('Access-Control-Allow-Credentials','true'), 
    res.header('Access-Control-Allow-Methods', 'PUT, GET, POST, DELETE, OPTIONS');
    res.header("Access-Control-Allow-Headers", "X-Requested-With");
    res.header('Access-Control-Allow-Headers', 'Content-Type');
    res.end("hello")
})

app.listen("4567",()=>{
    console.log("监听成功.....")
})

webSocket

因为同源策略对于websocket完全不适用,因此可以通过它打开到任何站点的连接。
使用websocket你需要考虑以下两个问题:

  • 成本问题,需要考虑你是否有自由度建立和维护 Web Sockets 服务器。
  • 需不需要双向通信

nginx

nginx是最方便的,就是配置参数而已,这里成本就是学习成本。

总结

以上所讲这几种跨域解决方案,目前最常用的就是cors和nginx,不过使用哪种方案需要根据场景来决定的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值