Bootstrap

前端跨域有几种方式?对应实例以及实现的具体方法?实现原理?跨域产生原因?

一、前端跨域产生的原因?

导致跨域的根本原因是请求浏览器的同源策略导致的

浏览器有同源策略限制,协议、域名、端口号三者一样就是同源,三者只要有一个不同就是跨域。

二、什么是同源策略?

同源策略(same origin policy)是netScape(网景)提出的一个安全策略,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。具体表现为浏览器在执行脚本前,会判断脚本是否与打开的网页是同源的,判断协议、域名、端口是否都相同,相同则表示同源。其中一项不相同就表示跨域访问。会在控制台报一个CORS异常,目的是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。

浏览器采用同源策略,在没有明确授权的情况下,禁止页面加载或执行与自身不同源的任何脚本。

三、跨域常见的几种方式有哪些?

目录

1、JSONP (不常用)

2、CORS 跨域资源共享 (前端不需要做任何改变)

3、proxy反向代理  (常用)

4、Nginx 代理(前端配置好,产品化去处理)

5、postMessage跨域消息传递

6、WebSocket协议跨域

7、document.domain + iframe

8、location.hash + iframe

9 、window.name + iframe


四、具体解决跨域的实现实例?

1、JSONP (不常用)

原理:是利用<script>标签不存在跨域请求的限制

缺点:1、只能处理get请求 2、通过URl携带参数容易被劫持,不安全

举例:也可以动态的去创建<script>标签,原理在这随意发挥,只要是<script>标签即可

文件1:index.html 

<script src="./index.jsonp.js"></script>

 文件2:index.jsonp.js 

$.ajax({
    url: 'http://127.0.0.1:8001/list',
    method: 'get',
    dataType: 'jsonp', //=>执行的是JSONP的请求
    success: res => {
        console.log(res);
    }
}); // 后端接受请求参数,并返回客户端,客服端拿到数据后执行函数

2、CORS 跨域资源共享 (前端不需要做任何改变)

原理:后端设置可访问的请求源

缺点:1、设置具体地址,有局限性  2、设置多源(*)就不能允许携带cookie了

举例:

前端发送请求(axios是基于promise封装)

axios.get('http://127.0.0.1:3001/user/list')
            .then(result => {
                console.log(result);
            })

后端设置相关请求: *号为多源,也可以替换为具体地址

app.use((req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Credentials", true);
    res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization, Accept,X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,HEAD,OPTIONS");
    if (req.method === 'OPTIONS') {
        res.send('OK!');
        return;
    }
    next();
});

3、proxy反向代理  (常用)

非脚手架搭建,则修改webpack.config.js项目。若是直接用脚手架搭建,则修改vue.config.js

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目标接口
            changeOrigin: true,
            secure: false,  // 当代理某些https服务报错时用
            cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
        }],
        noInfo: true
    }
}

4、Nginx 代理(前端配置好,产品化去处理)

通过请求到Nginx,让Nginx将这个请求发送到真正的服务器

推荐:Nginx代理功能与负载均衡详解icon-default.png?t=N7T8https://www.cnblogs.com/knowledgesea/p/5199046.html

# 工做进程的数量
worker_processes  1;
events {
    worker_connections  1024; # 每一个工做进程链接数
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # 日志格式
    log_format  access  '$remote_addr - $remote_user [$time_local] $host "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" "$clientip"';
    access_log  /srv/log/nginx/access.log  access; # 日志输出目录
    gzip  on;
    sendfile  on;

    # 连接超时时间,自动断开
    keepalive_timeout  60;

    # 虚拟主机
    server {
        listen       8080;
        server_name  localhost; # 浏览器访问域名

        charset utf-8;
        access_log  logs/localhost.access.log  access;

        # 路由
        location / {
            root   www; # 访问根目录
            index  index.html index.htm; # 入口文件
        }
    }

    # 引入其余的配置文件
    include servers/*;
}

5、postMessage跨域消息传递

postMessage(data,origin)方法接受两个参数:

data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据-postMessage
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };
 
    // 接受domain2返回数据-addEventListener
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>
<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);
 
        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;
 
            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

6、WebSocket协议跨域

定义:WebSocket Protocol 是HTML5一种新的协议。不同于Http协议,它实现了浏览器与服务器全双工通信,同时允许跨域通讯,能够支持服务器和客户端双方都主动推送消息给对方。

原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容

前端代码

<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.domain2.com:8080');
 
// 连接成功处理
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.'); 
    });
});
 
document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

后端代码

var http = require('http');
var socket = require('socket.io');
 
// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});
 
server.listen('8080');
console.log('Server is running at port 8080...');
 
// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });
 
    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

7、document.domain + iframe

定义:该方案适用于:主域相同,子域名不同的情况。对于一些有产品体系的大公司而言,不同的页面可能在不同的服务器上,这些服务器的域名不同,但拥有同样的上级域名。如www.qq.com, im.qq.com, user.qq.com,这些服务器上的页面就可以用domain来实现跨域数据访问

使用方式

通过js将两个页面都强制设定同一个基础主域,来实现同域的效果

// 主窗口
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
    document.domain = 'domain.com';
    var user = 'admin';
</script>

// 子窗口
<script>
    document.domain = 'domain.com';
    // 获取父窗口中变量
    console.log('get js data from parent ---> ' + window.parent.user);
</script>

8、location.hash + iframe

定义:实现原理: a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

// a.html
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
 
    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

// b.html
<iframe id="iframe" src="http://www.domain1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
 
    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

// c.html
<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

9 、window.name + iframe

定义:window.name可以在不同的页面(甚至不同的域名)加载后依然存在,并且可以支持非常长的name值(2MB)

var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');
 
    // 加载跨域的页面,跨域页面会将数据写入name中
    iframe.src = url;
 
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.name,第二次则是通过同源网页拿到name
    iframe.onload = function() {
        if (state === 1) {
            // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
            callback(iframe.contentWindow.name);
            destoryFrame();
 
        } else if (state === 0) {
            // 第1次onload(跨域页)成功后,切换到同域代理页面
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1;
        }
    };
 
    document.body.appendChild(iframe);
 
    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destoryFrame() {
        iframe.contentWindow.document.write('');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
};
 
// 请求跨域b页面数据
proxy('http://www.domain2.com/b.html', function(data){
    alert(data);
});

;