第06章 跨域

143 阅读4分钟

参考链接:www.cnblogs.com/sdcs/p/8484…

一、 概述

跨域是指一个域下的文档或脚本试图去请求另一个域下的资源,这里跨域是广义的。其实我们通常所说的跨域是狭义的,是由浏览器同源策略限制的一类请求场景。

# 什么是同源策略?

同源策略/SOP(Same origin policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。

同源策略限制以下几种行为:

1)CookieLocalStorageIndexDB 无法读取 2)DOMJavaScript 对象 无法获得 3)Ajax请求不能发送

既然有安全问题,那为什么又要跨域呢? 举个例子,假如公司内部有多个不同的子域,当需要从一个子域访问另一个子域的资源时就需要跨域。

二、JSONP

通常为了减轻web服务器的负载,我们把 jscssimg 等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建 script,再请求一个带参网址实现跨域通信。

1. 后端代码

const http = require("http");
const url  = require("url");
http.createServer((req, res) => {
    if(req.url === "/favicon.ico") return;
    let {pathname, query} = url.parse(req.url, true);
    switch(pathname) {
        case "/info": {
            // 定义返回数据
            let _params = {name: "木子李", tel: "17398888669", job: "前端工程师"};
            // 获取回调函数名
            let _funcName = query.callback;
            // 设置响应头
            res.writeHead(200, { 'Content-Type': 'text/javascript' });
            // 调用回调函数并将参数从后台传递给前台
            res.end(`${_funcName}(${JSON.stringify(_params)})`);
        }break;
    }
}).listen(80, "0.0.0.0"); 
// 提示用户服务器相关信息
console.log("server running at http://127.0.0.1");

2. 原生实现

<!-- 1. 原生实现 -->
<script>
    // 动态创建script标签
    const script = document.createElement('script');
    script.type = 'text/javascript';
    // 传参并指定回调执行函数为onBack
    script.src = `http://127.0.0.1/info?name=木子李&callback=onBack`;
    document.body.appendChild(script);
    // 回调执行函数
    function onBack(res) {
        console.log(res);
    }
</script>

3. jquery ajax

$.ajax({
    url: "http://127.0.0.1/info",
    type: "GET",
    dataType: "jsonp",  // 请求方式为jsonp
    jsonpCallback: "onBack",    // 自定义回调函数名
    data: {name: "木子李"}
});

4. vue.js

new Vue({
    el: "#app",
    mounted() {
        this.$http.jsonp("http://127.0.0.1/info", {
            params: {name: "木子李"},
            jsonp: "callback"
        }).then(res => {
            console.log(res.body);
        })
    },
})

提示:需下载 vue-resource,并在vue之后引入。

jsonp缺点:只能实现 get 一种请求。

三、CROS

服务端设置 Access-Control-Allow-Origin 即可。

# nodeJS

res.setHeader('Access-Control-Allow-Origin', '*');

# express

app.all("*", (req, res, next) => {
    //设置允许跨域的域名,*代表允许任意域名跨域
    res.header("Access-Control-Allow-Origin","*");
    //允许的header类型
    res.header("Access-Control-Allow-Headers","content-type");
    //跨域允许的请求方式 
    res.header("Access-Control-Allow-Methods","DELETE,PUT,POST,GET,OPTIONS");
    if (req.method.toLowerCase() == 'options')
        res.send(200);  // 让options尝试请求快速结束
    else
        next();
});

# egg.js

① 安装依赖

$ npm install egg-cors

**② 在plugin.js中设置开启cors **

module.exports = {
  cors: {
    enable: true,
    package: "egg-cors",
  },
};

③ 在 config.{env}.js 中配置,注意配置覆盖的问题

// 安全性配置
config.security = {
  // 支持post
  csrf: {
    enable: false,
    ignoreJSON: true,
  },
  domainWhiteList: [],
};

// 处理跨域
config.cors = {
  origin: "*",
  allowMethods: "GET,HEAD,PUT,POST,DELETE,PATCH",
};

四、中间件代理跨域 DevServer

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

五、WebSocket 协议跨域

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器双向通信,同时允许跨域通讯,是server push技术的一种很好的实现。原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

1. 前端代码

<script src="./node_modules/socket.io-client/dist/socket.io.js"></script>
<script>
    let socket = io('http://127.0.0.1:8080');
    // 监听服务端消息
    socket.on('/message', data => {
        console.log('data from server: ---> ' + data);
    });
    setTimeout(() => {
        socket.emit("/message", JSON.stringify({name: "木子李"}));
    }, 3000);

</script>

2. 后端代码

const http = require("http");
const socket = require('socket.io')(http);
const server = http.createServer((req, res) => {
    res.writeHead(200);
    res.end();
}).listen('8080', "0.0.0.0");
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', client => {
    console.log('Client socket is open.'); 
    // 接收信息
    client.on('/message', function(data) {
        // 发送消息
        client.emit('/message', JSON.stringify({
            name: "木子李",
            tel: "17398888669",
            job: "前端工程师"
        }));
        console.log('data from client: ---> ', data);
    });
    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

六、代理跨域 Nginx

参考:www.cnblogs.com/lovesong/p/…

七、postMessage

  • 利用postMessage不能和服务端交换数据,只能在两个窗口(iframe)之间交换数据
  • 两个窗口能通信的前提是,一个窗口以iframe的形式存在于另一个窗口,或者一个窗口是从另一个窗口通过window.open()或者超链接的形式打开的(同样可以用window.opener获取源窗口)

参考链接 >>