前端解决跨域方法

125 阅读1分钟

同源策略

什么是同源策略 : 协议 、 域名 、 端口 一致

  • 协议是什么? 如 : http 、https
  • 域名是什么? 如 : baidu.com 、 juejin.cn 、 127.0.0.1 ...
  • 端口号是什么 ? 域名或IP地址冒号后边

image.png

当协议、或域名 、或端口 不一样时 . 即跨域

为什么浏览器不支持跨域

  • cookie 、 LocalStorage 防止被窃取
  • DOM元素 防止被修改
  • ajax 防止数据链接被盗用

实现跨域

  • jsonp
    • 在html标签中 img 、 script 标签的src属性不受同源策略影响,随意引用.
    • jsonp是利用script标签的src属性引用跨域的js文件.并将回调函数写在引用中.跨域的js文件执行后,调用回调函数执行到页面中
    • 缺点
      1. 只能get请求,不支持get/put/delete
      2. 容易被xss攻击
//我是个本地的页面
...
<script>
    function callback(args){
        console.log(args)
    }
</script>
<script src="http://xxx.com/a.js?cb=callback"></script>
...

//我是http://xxx.com/a.js
...
function getQueryString(name) {
    let reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)", "i");
    let r = window.location.search.substr(1).match(reg);
    if (r != null) {
        return decodeURIComponent(r[2]);
    };
    return null;
}
var cbFun =  getQueryString('cb') || null;
cbFun && cbFun([1,2,3])
...

  • cors
    • 主要是服务端做的操作,浏览器的请求已经发送到服务器,但被浏览器屏蔽.
// 以nodeJs express框架 解决为例

// 浏览器报错 :  ‘Access-Control-Allow-Origin‘ header is ....
// 服务器中间件允许所有请求
...
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Origin',"*")
    next()
})
...

// 服务器中间件设置指定可访问的源
...
const passOrigin = ["http://xxxx.com"]
app.use((req,res,nest)=>{
    const headersOrigin = req.headers.origin;
    if(passOrigin.includes(headersOrigin)){
        res.setHeader('Access-Control-Allow-Origin',headersOrigin)
    }
    next()
})
...


// 浏览器报错 :  ‘Access-Control-Allow-Headers‘
// 原因是浏览器发起 ajax 请求时 带有header头
// 客户端请求
$.ajax({
    url:"http://xxx.com/xx",
    type:"POST",
    headers:{
        a:1,
        b:2
    }
});
// 服务器中间件处理可接收任意请求头
...
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Headers',"*")
    next()
})
...
//服务器中间件处理接收指定请求头 例: 接收请求头中的a 和 b
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Headers',"a,b")
    next()
})


// 浏览器报错 : ‘Access-Control-Allow-Methods‘
// 原因: 客户端发送请求时 使用了PUT / DELETE 等方法, GET和POST 为默认方法 不会报错 

// 客户端请求
$.ajax({
    url:"http://xxx.com/xx",
    type:"PUT",
    data:{
        a:1,
        b:2
    }
});
// 服务器中间件增加支持请求的方法
...
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Methods',"PUT,DELETE")
    next()
})
// 设置完后, 客户端请求会发送2个 
// 第一个请求的 Request Method: OPTIONS .是预测请求.用于试探服务器是否允许 PUT方法 可以跨域
// 此时服务器端中间件可以增加处理
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Methods',"PUT,DELETE")
    res.setHeader('Access-Control-Max-Age',6); //预测最大通过时间.以秒为单位
    if(req.method == "OPTIONS"){
        res.end() // 如果是OPTIONS请求 不做任何处理
    }
    next()
})

...


// 浏览器报错 :  ‘Access-Control-Allow-Credentials‘
// 原因 告知浏览器是否可以将对请求的响应暴露给前端
// 服务端中间件处理 , 允许携带cookie
app.use((req,res,next)=>{
    res.setHeader('Access-Control-Allow-Credentials',true)
    next()
})



// 浏览器报错 Refused to get unsafe header "xx"
// 原因是 服务端给客户端返回数据时 使用res.setHeader("xx","1"); 客户端想获取服务器返回的header中的数据

// 服务器中间件处理
app.use((req,res,next)=>{
    //服务器端支持客户端获取返回header中xx字段 , 设置多个 实用","分隔
    res.setHeader('Access-Control-Allow-Expose-Headders',"xx"); 
    next()
})

  • postMessage
    • 用于iframe间页面通信
// a.html

<body>
    <iframe src="http://xxx.com/b.html" id="frame" onload="onload()"></iframe>
</body>
<script>
    function onload(){
        const frame = document.getElementById("frame");
        frame.contentWindow.postMessage("来自a.html的问候","http://xxx.com")
        
        window.onmessage = function (e){
            // 接收了b.html传过来的数据
            console.log(e.data)
        }
    }

</script>



//b.html

<script>
window.onmessage = function(e){
    // 接收了a.html传过来的数据
    console.log(e.data)
    
    // 给a.html回消息
    e.source.postMessage("来自b.html的问候",e.origin)
}
<script>


  • document.domain
    • 仅限跨域的两个页面为一级域名和二级域名的关系
    • 例如: xx.com 和 music.xx.com 这样的
// xx.com/a.html
<body>
    <iframe src="music.baidu.com/b.html" id="frame" onload="load()"></iframe>
</body>
<script>
    document.domain = "xx.com"
    function load(){
        const frame = document.getElementById("frame");
        // 获取b.html中声明的a属性
        console.log(frame.contentWindow.a)  //100
        
    }
</script>

// music.xx.com/b.html
<script>
    document.domain = "xx.com"
    var a = 100;
</script>
  • window.name
    • 浏览器默认的一个属性 , 属性默认值 “” 为空字符串
// a.html 和 b.html为同域 , c.html为跨域
// b.html为空页面

// http:xxx:3000/a.html
<body>
    <iframe src="http:xxx:5000/c.html" onload="load()" id="frmae"></iframe>
</body>
<script>
     var isFirst = true;
    function load(){
        const frmae = document.getElementById("frmae")
        if(isFirst){
            frmae.src = "http:xxx:3000/b.html";
            isFirst = false;
        }else{
            console.log(frmae.contentWindow.name)   // 这是c.html
        }
        
    }
</script>


// http:xxx:5000/c.html
<script>
    window.name = "这是c.html"
</script>
  • location.hash
    • 网址路径后面的hash值可以用来通信
// http:xxx:3000/a.html
<body>
    <iframe src="http:xxx:5000/c.html#from_a_html" id="frmae"></iframe>
</body>
<script>
    window.onhashchange = function(){
        console.log(location.hash) // from_c_html
    }
</script>


// http:xxx:3000/b.html
<script>
   // 将自己接收到的 hash值 传给上级引用的上级
    window.parent.parent.location.hash = location.hash;
</script>

// http:xxx:5000/c.html
<script>
    console.log(location.hash) // from_a_html
    const iframeEl = document.createElement("iframe");
    iframeEl.src = "http:xxx:3000/b.html#from_c_html";
    document.body.appendChild(iframeEl)

</script>



location / {  
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
    add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';

    if ($request_method = 'OPTIONS') {
        return 204;
    }
} 

  • websocket
    • 使用socket.io
    • 原理是借助socket服务 向跨域的页面中发送消息
// a.html
<script>
    const socket = new WebSocket("ws://xxx:5000");
    socket.onopen = function(){
        socket.send("向服务器发送一个信息")
    }
    socket.onmessage = function(data){
        console.log(data) // "来自服务器的消息"
    }
</script>


// node server.js
const webSocket = require("ws");
const wss = new webSocket.Server({port:5000})
wss.on("connection",(ws)=>{
    ws.on("message",(data)=>{
        console.log(data); // "向服务器发送一个信息"
    })
    ws.send("来自服务器的消息")
})