跨域的解决方案-2理论篇

358 阅读2分钟

前言:项目中的跨域问题还是没有解决。这里是我看的周啸天老师的学习笔记。

跨域问题的产生以及价值意义

跨域:非同源策略请求

  • 同源策略请求 ajax/fetch
同源策略限制
:同源策略限制一个资源地址加载的文档或脚本与另一个资源地址的资源进行交互。这是浏览器的一个用于隔离潜在恶意文件的关键安全机制。它的存在可以保护用户隐私信息,防止伪造身份等(读取cookie)
同源策略限制的内容有:
1.cookie,local storage,indexedDB等存储型内容
2.不允许进行DOM节点的操作
3.不能进行AJAX请求

服务器拆分

  • web服务器:静态资源
  • data服务器:业务逻辑和数据分析
  • 图片服务器

协议、域名、端口号 三者都一样就是同源,只要有一个不一样就是跨域。

1.jsonp跨域解决方案的底层原理

  • script
  • img
  • link
  • iframe
  • ... =>这些标签不存在跨域请求的限制

jsonp和ajax相比。都是客户端向服务器发送请求,从服务器端获取数据的方式。但Ajax属于同源策略,而jsonp属于非同源策略。

image.png

jsonp的流程
1.声明一个回调函数,其函数名当作参考值,传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
2.创建一个<script>标签,把那个跨域的api数据的接口地址,赋值给script的src,还要在这个地址中向服务器传递改函数名(可以通过问号传参 :?callback=func)
3.服务器接收到请求之后,需要进行特殊的处理:把传递进来的函数名和它需要给你爹数据拼接成一个字符串
4.最后,服务器把准备的数据通过http协议返回给客户端,客户端载调用执行之前声明的回调函数func,对返回的数据进行操作。

func是客户端注册的回调的函数,目的是获取跨域服务器上的jsonp数据之后,对数据再进行处理。
$ajax({
    url: 'http://127.0.0.1:8001/list',
    method: 'get',
    dataType: 'jsonp',//=>执行的是jsonp请求
    success: res => {
        consple.log(res)
    }
})

问题:jsonp只能处理get请求

2.CORS跨域资源共享

整个cors通信过程,都是浏览器自动完成,不需要用户参与。cors通信的关键是服务器。只要服务器实现了cors接口,就可以跨源通信。

在响应头上添加Access-Control-Allow-Origin属性,指定同源策略的地址。同源策略默认地址是网页本身。只要浏览器检测到响应头上带上了cors,并且允许的源包括了本网站,那么就不会拦截请求。

  1. 客户端(发送Ajax/fetch请求)
axios.get('http://127.0.0.1:3001/user/list')
    .then(result => {
        console.log(result);
    })
-----------------------------------------------
axios.defaults.baseURL = 'http://127.0.0.1:8088';
axios.defaults.withCredentials = true;
axios.defaults.headers['content-Type'] = 'application/x-www-form-urlencoded';
axios.defaults.transformRequest = funxtion(data) {
    if(!data) return data;
    let result = '';
    for (let attr in data) {
        if(!data.hasOwnProperty(attr)) break;
        result += '&${attr}=${data[attr]}';
    }
    return result.substring(1);
};
axios.interceptors.response.use(function onFulfilled(response)
{
    return response.data;
}, function onRejected(reason) {
    return Promise.reject(reason);
});
axios.defaults.validateStatus = function (status) {
    return /^(2|3)\d{2}$/.test(status);
}
  1. 服务器设置相关头信息(需要处理options试探性请求)
.md文件

app.use((req, res, next) => {
    res.header("Access-Control0Allow-Origin","http://localhost:8000");
    //=> * (允许多源的意思,但不允许携带cookie),具体地址(http://localhost:8000: 写一个)
    res.header("Access-Control-Allow-Credentials", true);
    res.header("Access-Control-Allow-Headers","Conntent-Type,Content-Length,Authorization,Accept,X-Requested-Width")
    res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,HEAD,OPTIONS");
    if(req.method === 'OPTIONS'{
        res.send('ok');
        return;
    }
    next();
})

3.基于http proxy实现跨域请求

==>webpack webpack-dev-server

在webpack.config.js文件里面修改proxy,不需要服务器做处理,前端可以独自完成。(我们项目就是采用这种方法,我会在下一篇中详细阐述实现过程)

4.基于postMessage实现跨域处理

如果两个网页不同源,就无法拿到对方的dom。经典的例子就是iframe窗口与window.open打开的窗口,他们与父窗口无法通信。html5为了解决这个问题,引入了一个全新的api:跨文档通信api。这个api为window对象新增了一个window.postMessage方法,允许跨窗口通信,无论两个窗口是否同源。

postMessage方法的第一个参数是具体的信息内容,第二个参数是接受信息的窗口的源(origin),即“协议+域名+端口”。也可设置为*,表示不限制域名,向所有窗口发送。

#server1.js
let express = require('express'),
    app = express();
app.listen(1001, _=> {
    console.log('ok!');
})
app.get('/list', (req, res) => {
    let {
        callback = Function.prototype
    } = req.query;
    let data = {
        code: 0,
        message: '珠峰培训’
    };
    res.send('${callback}(${JSON.stringify(data)
    })');
});
//静态资源的处理
app.use(express.static('./'));

B页面
//=>监听A发过来的信息
window.onmessage = function (ev) {
    console.log(ev.data);
    //=>ev.source:A
    ev.source.postMessage(ev.data+'@@', ev.origin);
}

A页面
<body>
    <iframe id="iframe" src="http://127.0.0.1:1002/MESSAGE/B.html"></iframe>
    <script>
        iframe.onload = function () {
            iframe.contentwindow.postMessage('猪猪','http://127.0.0.1:1002/MESSAGE/B.html'
        }
//=>监听B传递的信息
window.onmessage = function (ev) {
    console.log(ev.data);//猪猪@@
}

5.基于iframe的跨域请求方案

window.name/document.domin/location.hash

docuemnt.domain + iframe

只能实现同一个主域,不同的子域之间的操作。

父页面A www.zhuzhu.cn/B.html

<iframe src="http:/school.zhuzhu.cn/B.html"></iframe>
<script>
    document.domain = 'zhuzhu.cm';
    var user = 'admin';
</script>

子页面B http:/school.zhuzhu.cn/B.html

<script>
    document.domain = 'zhuzhu.cn'
    alert(window.parent.user);
</script>
#proxy服务器
sercer {
    listen        80;#监听的端口号
    server_name www.hufeng.com;
    location / {
        proxy_pass www.hufeng.cn; #反向代理
        proxy_cookie_demo www.hufeng.cn www.hufeng.com;
        add_header Access-Control-Allow-Origin www.hufeng.cn;
        add_header Access-Control-Allow-Credentials true;
    }
}

window.name + iframe

需要三个页面

页面A

let proxy = function(url, callback) {
    let count = 0;
    let iframe = document.createElement('iframe');
    iframe.src = url;
    iframe.onload = function() {
        if(count === 0){
            iframe.conyentWindow.location = 'http://www.zhuzhu.cn/proxy.html';
            count++;
            return;
        }
        callback(iframe.contentWindow.name);
    };
    document.body.appendChild(iframe);
};

页面B

<script>
    //=>服务器端需要返回给A的信息都在window.name中存储着
    window.name = '猪猪'

location.hash + iframe

  • A和C同源
  • A和B非同源

6.ngnix反向代理

不需要前端处理,是服务器部署的事情。

  • www.hufeng.cn -> www.hufeng.com
  • 启一个linux服务器,在linux服务器里面安装一个nginx,在里面配置一个web服务器,在服务器里设置反向代理。

7.websocket协议跨域

是一个客户端与服务器实时通信的处理。前端处理

websocket是HTML5的一个持久化协议,实现了浏览器与服务器的全双工通信,同时也是一种跨域的解决方案。websocket和HTTP都是应用层协议,都基于tcp协议。

<script src="./socket.io.js"></script>
<script>
let socket = io('http://www.127.0.0.1:3001/');
//=>连接成功处理
socket.on('connect',function() {
    //=>监听服务端信息
    socket.on('message', function(msg) {
        console.log('data from server:' + msg);
    })
    //=>监听服务端关闭
    socket.on('disconnect', function(){
        console.log('server socket has closed!');
    })
})
//=>发送消息给服务器端
socket.send("zhuzhu")
</script>

服务器端处理

//=>监听socket连接:server是服务器创建的服务
socket.listen(server).on('connection', function(client) {
    //=>接收信息
    client.on('message', function(msg) {
        //=>msg客户端传递的信息
        //...
        client.send(msg+'@@');
    });
    //=>断开处理
    client.on('disconnect', function() {
        console.log('client socket has closed!')
    })
})