9 common front-end cross-domain solutions (detailed)

1. What is cross domain?

First of all, it is stated that cross-domain is the behavior of browser interception. The request has been sent to the backend, and the response data returned by the backend is intercepted by the browser. This is the cross-domain process.

In the front-end field, cross-domain means that the browser allows cross-domain requests to be sent to the server, thereby overcoming the limitation that Ajax can only be used on the same origin .

What is the Same Origin Policy?

The same-origin policy is a convention introduced by Netscape in 1995 to browsers. It is the core and most basic security function of browsers. Without the same-origin policy, browsers are vulnerable to attacks such as XSS and CSFR. The so-called homology means that "protocol + domain name + port" are the same, even if two different domain names point to the same IP address, they are not of the same origin.
The same-origin policy restricts the following behaviors:

  • Cookies, LocalStorage and IndexDB cannot be read
  • DOM and JS objects cannot be obtained
  • AJAX request could not be sent

2. Common cross-domain scenarios

URL illustrate Whether to allow communication
http://www.domain.com/a.js http://www.domain.com/b.js Same domain name, different files or paths allow
http://www.domain.com:8000/a.js http://www.domain.com/b.js Same domain name, different ports not allowed
http://www.domain.com/a.js https://www.domain.com/b.js Same domain name, different protocols not allowed
http://www.domain.com/a.js http://192.168.4.12/b.js The domain name and the domain name correspond to the same ip not allowed
http://www.domain.com/a.js http://x.domain.com/b.js http://domain.com/c.js Same main domain, different subdomains not allowed
http://www.domain1.com/a.js http://www.domain2.com/b.js different domain names not allowed

Three, 9 kinds of cross-domain solutions

1. JSONP cross-domain

The principle of jsonp is to use the <script> tag without cross-domain restrictions. Through the src attribute of the <script> tag, send a GET request with a callback parameter, and the server will piece together the data returned by the interface into the callback function and return it to the browser. Parse and execute, so that the front end gets the data returned by the callback function.

  1. Native JS implementation:
 <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';
 
    // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);
 
    // 回调执行函数
    function handleCallback(res) {
    
    
        alert(JSON.stringify(res));
    }
 </script>

The server returns as follows (the global function is executed when it returns):

handleCallback({
    
    "success": true, "user": "admin"})

2) jquery Ajax implementation:

$.ajax({
    
    
    url: 'http://www.domain2.com:8080/login',
    type: 'get',
    dataType: 'jsonp',  // 请求方式为jsonp
    jsonpCallback: "handleCallback",  // 自定义回调函数名
    data: {
    
    }
});

3) Vue axios implementation:

this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {
    
    
    params: {
    
    },
    jsonp: 'handleCallback'
}).then((res) => {
    
    
    console.log(res); 
})

Backend node.js code:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();
 
server.on('request', function(req, res) {
    
    
    var params = querystring.parse(req.url.split('?')[1]);
    var fn = params.callback;
 
    // jsonp返回设置
    res.writeHead(200, {
    
     'Content-Type': 'text/javascript' });
    res.write(fn + '(' + JSON.stringify(params) + ')');
 
    res.end();
});
 
server.listen('8080');
console.log('Server is running at port 8080...');

Note: Disadvantages of jsonp: only one kind of get request can be sent.

2. Cross-Origin Resource Sharing (CORS)

CORS is a W3C standard, and its full name is "Cross-origin resource sharing".
It allows browsers to send XMLHttpRequest requests to cross-origin servers, thereby overcoming the limitation that AJAX can only be used on the same origin.
CORS requires both browser and server support. Currently, all browsers support this function, and the IE browser cannot be lower than IE10.

Browsers divide CORS cross-domain requests into simple requests and non-simple requests.
As long as the following two conditions are met at the same time, it is a simple request

(1) Use one of the following methods:

  • head
  • get
  • post

(2) The requested Heder is

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type: limited to three values: application/x-www-form-urlencoded, multipart/form-data, text/plain

If the above two conditions are not met at the same time, it is a non-simple request. Browsers treat these two differently.

simple request

For simple requests, the browser makes a CORS request directly. Specifically, an Origin field is added to the header information.

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

In the above header information, the Origin field is used to indicate which source (protocol + domain name + port) this request comes from. Based on this value, the server decides whether to agree to the request.
The response header fields set by the CORS request all start with Access-Control-:

1) Access-Control-Allow-Origin : Mandatory
Its value is either the value of the Origin field at the time of the request, or a *, which means accepting requests from any domain name.

2) Access-Control-Allow-Credentials : optional
Its value is a Boolean value, indicating whether to allow sending Cookie. By default, cookies are not included in CORS requests. If it is set to true, it means that the server expressly allows that Cookie can be included in the request and sent to the server together. This value can only be set to true. If the server does not want the browser to send cookies, just delete this field.

3) Access-Control-Expose-Headers :
When CORS requests are optional, the getResponseHeader() method of the XMLHttpRequest object can only get 6 basic fields: Cache-Control, Content-Language, Content-Type, Expires, Last-Modified, Pragma. If you want to get other fields, you must specify them in Access-Control-Expose-Headers. The above example specifies that getResponseHeader('FooBar') can return the value of the FooBar field.

non simple request

A non-simple request is a request that has special requirements on the server, for example, the request method is PUT or DELETE, or the type of the Content-Type field is application/json. For CORS requests that are not simple requests, an HTTP query request will be added before formal communication, which is called a "preflight" request (preflight).

Pre-check request
The request method used for the "pre-check" request is OPTIONS , indicating that the request is used for inquiry. In the request header information, the key field is Origin , indicating which source the request comes from. Except for the Origin field, the "pre-check" request The header information includes two special fields.

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0..

1) Access-Control-Request-Method : Mandatory Used
to list which HTTP methods will be used by the browser's CORS request, the above example is PUT.

2) Access-Control-Request-Headers : Optional
This field is a comma-separated string, specifying the additional header fields that will be sent by the browser for CORS requests. The above example is X-Custom-Header.

Response to preflight request
After receiving the "preflight" request, the server checks the Origin, Access-Control-Request-Method, and Access-Control-Request-Headers fields, confirms that cross-origin requests are allowed, and responds.

In the HTTP response, except for the key Access-Control-Allow-Origin field, other CORS-related fields are as follows:

1) Access-Control-Allow-Methods : Required
Its value is a string separated by commas, indicating all cross-domain request methods supported by the server. Note that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple "preflight" requests.

2) Access-Control-Allow-Headers
If the browser request includes the Access-Control-Request-Headers field, the Access-Control-Allow-Headers field is required. It is also a comma-separated string indicating all header fields supported by the server, not limited to those requested by the browser in "preflight".

3) Access-Control-Allow-Credentials : Optional
This field has the same meaning as that of a simple request.

4) Access-Control-Max-Age : optional It is
used to specify the validity period of this pre-check request, in seconds.

CORS cross domain example

1) Front-end settings :

Native ajax:

var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容
 
// 前端设置是否带cookie
xhr.withCredentials = true;
 
xhr.open('post', 'http://www.domain2.com:8080/login', true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('user=admin');
 
xhr.onreadystatechange = function() {
    
    
    if (xhr.readyState == 4 && xhr.status == 200) {
    
    
        alert(xhr.responseText);
    }
};

jquery ajax:

$.ajax({
    
    
    ...
   xhrFields: {
    
    
       withCredentials: true    // 前端设置是否带cookie
   },
   crossDomain: true,   // 会让请求头中包含跨域的额外信息,但不会含cookie
    ...
});

2) Server settings:

nodejs code

var http = require('http');
var server = http.createServer();
var qs = require('querystring');
 
server.on('request', function(req, res) {
    
    
    var postData = '';
 
    // 数据块接收中
    req.addListener('data', function(chunk) {
    
    
        postData += chunk;
    });
 
    // 数据接收完毕
    req.addListener('end', function() {
    
    
        postData = qs.parse(postData);
 
        // 跨域后台设置
        res.writeHead(200, {
    
    
            'Access-Control-Allow-Credentials': 'true',     // 后端允许发送Cookie
            'Access-Control-Allow-Origin': 'http://www.domain1.com',    // 允许访问的域(协议+域名+端口)
            /* 
             * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现),
             * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问
             */
            'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'  // HttpOnly的作用是让js无法读取cookie
        });
 
        res.write(JSON.stringify(postData));
        res.end();
    });
});
 
server.listen('8080');
console.log('Server is running at port 8080...');

3. nginx proxy cross-domain

The nginx proxy cross-domain is essentially the same as the CORS cross-domain principle. The request response header Access-Control-Allow-Origin... and other fields are set through the configuration file.

1) nginx configuration solves iconfont cross-domain

Cross-domain access to regular static resources such as js, css, and img by browsers is permitted by the same-origin policy, but iconfont font files (eot|otf|ttf|woff|svg) are exceptions. At this time, the following configuration can be added to the nginx static resource server .

location / {
    
    
  add_header Access-Control-Allow-Origin *;
}

2) nginx reverse proxy interface cross-domain

Cross-domain issues: The same-origin policy is only a security policy for browsers. The server calls the HTTP interface only using the HTTP protocol, and does not require the same-origin policy, so there is no cross-domain problem.

Implementation idea: Configure a proxy server with the same domain name as domain1 and a different port through Nginx as a springboard machine, reverse proxy access to domain2 interface, and modify the domain information in the cookie by the way, so as to facilitate the writing of the current domain cookie and realize cross-domain access.

Nginx specific configuration:

#proxy服务器
server {
    
    
    listen       81;
    server_name  www.domain1.com;
 
    location / {
    
    
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
        index  index.html index.htm;
 
        # 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #当前端只跨域不带cookie时,可为*
        add_header Access-Control-Allow-Credentials true;
    }
}

4. nodejs middleware proxy cross-domain

The node middleware implements cross-domain proxying. The principle is roughly the same as that of nginx. Data forwarding is realized by starting a proxy server. You can also modify the domain name in the cookie in the response header by setting the cookieDomainRewrite parameter to realize the cookie writing of the current domain, which is convenient. Interface login authentication.

1) Cross-domain of non-vue framework

Use node + express + http-proxy-middleware to build a proxy server.
Front-end code:

var xhr = new XMLHttpRequest();
 
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
 
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();

Middleware server code:

var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
 
app.use('/', proxy({
    
    
    // 代理跨域目标接口
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,
 
    // 修改响应头信息,实现跨域并允许带cookie
    onProxyRes: function(proxyRes, req, res) {
    
    
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },
 
    // 修改响应信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 可以为false,表示不修改
}));
 
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
 

2) Cross domain of vue framework

The project built by node + vue + webpack + webpack-dev-server, cross-domain request interface, directly modify the webpack.config.js configuration. In the development environment, the vue rendering service and the interface proxy service are the same webpack-dev-server, so there is no cross-domain between the page and the proxy interface.
Partial configuration of webpack.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
    }
}

5. document.domain + iframe cross-domain

This solution is limited to cross-domain application scenarios with the same primary domain and different subdomains. Realization principle: both pages use JS to force document.domain to be the basic main domain, and the same domain is realized.

1) Parent window: (http://www.domain.com/a.html)

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

1) Child window: (http://child.domain.com/a.html)

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

6. location.hash + iframe cross domain

Realization principle: A wants to communicate with b across domains, and realize it through the middle page c. For the three pages, use the location.hash of the iframe to pass values ​​between different domains, and communicate with each other through direct js access between the same domains.

Specific implementation: domain A: a.html -> domain B: b.html -> domain A: c.html, different domains of a and b can only communicate in one direction through the hash value, and domains of different b and c can only communicate one-way Direct communication, but c is in the same domain as a, so c can access all objects on page a through parent.parent.

1)a.html:(http://www.domain1.com/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>

2)b.html:(http://www.domain2.com/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>

3)c.html:(http://www.domain1.com/c.html)

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

7. window.name + iframe cross domain

The uniqueness of the window.name attribute: the name value still exists after loading different pages (even different domain names), and can support very long name values ​​(2MB).

1)a.html:(http://www.domain1.com/a.html)

var proxy = function(url, callback) {
    
    
    var state = 0;
    var iframe = document.createElement('iframe');
 
    // 加载跨域页面
    iframe.src = url;
 
    // onload事件会触发2次,第1次加载跨域页,并留存数据于window.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);
});

2)proxy.html:(http://www.domain1.com/proxy.html)

The intermediate proxy page is in the same domain as a.html, and the content can be empty.

3)b.html:(http://www.domain2.com/b.html)

<script>
    window.name = 'This is domain2 data!';
</script>

The src attribute of the iframe is transferred from the external domain to the local domain, and the cross-domain data is transferred from the external domain to the local domain by the window.name of the iframe. This cleverly bypasses the browser's cross-domain access restrictions, but at the same time it is a safe operation.

8. postMessage cross-domain

postMessage is an API in HTML5 XMLHttpRequest Level 2, and it is one of the few window attributes that can be operated across domains. It can be used to solve the following problems:

  • Data transfer for pages and new windows they open
  • Message passing between multiple windows
  • page with nested iframe messaging
  • Cross-domain data transfer in the above three scenarios

Usage: The postMessage(data, origin) method accepts two parameters:

  • data :
    The html5 specification supports any basic type or reproducible object, but some browsers only support strings, so it is best to serialize with JSON.stringify() when passing parameters.
  • origin : protocol + host + port number, can also be set to "*", which means it can be passed to any window, if you want to specify the same origin as the current window, set it to "/".

1)a.html:(http://www.domain1.com/a.html)

<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传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };
 
    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
    
    
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

2)b.html:(http://www.domain2.com/b.html)

<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>

9. Cross-domain WebSocket protocol

WebSocket protocol is a new protocol in HTML5. It realizes full-duplex communication between browser and server, and allows cross-domain communication at the same time, which is a good implementation of server push technology.
The native WebSocket API is not very convenient to use. We use Socket.io, which encapsulates the webSocket interface well, provides a simpler and more flexible interface, and provides backward compatibility for browsers that do not support webSocket.

1) Front-end code:

<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>

2) Nodejs socket background:

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.'); 
    });
});

summary

The above are 9 common cross-domain solutions, jsonp (only supports get requests, supports old IE browsers) is suitable for loading static resources such as js, css, img of different domain names; CORS (supports all types of HTTP requests, but browsing It is suitable for ajax cross-domain requests; Nginx proxy cross-domain and nodejs middleware cross-domain principles are similar. They both build a server and directly request the HTTP interface on the server side, which is suitable for front-end separation. The project adjusts the backend interface. document.domain+iframe is suitable for cross-domain requests with the same main domain name and different subdomain names. postMessage and websocket are both new features of HTML5, and their compatibility is not very good. They are only applicable to mainstream browsers and IE10+.

Guess you like

Origin blog.csdn.net/xiaolinlife/article/details/130982886