Common cross-domain solutions at the front end

create by db on 2019-12-31 14:31:32

Recently revised in 2021-7-5 18:05:59

Hello friends, if you think this article is good, please give a thumbs up or a star, your thumbs up and star are my motivation! Making the address

This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

preface

I hear and I fogorget.

I see and I remember.

I do and I understand.

References:

  • Front-end common cross-domain solutions (full) | think no – quiet DE precipitation

  • Probably the best cross-domain solution… . | the nuggets – just a siege of the lion

  • Simple | CSDN – sleepwalker_199 cross-domain cases

The body of the

What is cross-domain?

Cross-domain refers to a document or script in one domain trying to request a resource in another domain. Cross-domain is defined broadly here.

In a broad sense, cross-domain:

  1. Resource jumps: links, redirects, form submission

  2. Resource embedding: ,

  3. Script requests: JS-initiated Ajax requests, dom and JS object cross-domain operations, etc

In fact, what we usually mean by cross-domain is narrowly defined, a class of request scenarios that are restricted by the browser’s same-origin policy.

For example, if page A wants to obtain resources from page B. The protocol, domain name, port, and subdomain name of page A and page B are different, the access actions are cross-domain. However, browsers generally restrict cross-domain access for security, that is, they do not allow cross-domain resource requests.

Note: cross-domain access restrictions are browser restrictions. This is important to understand!!

What is the same origin policy?

The Same Origin Policy (SOP) is a convention introduced by Netscape into the browser in 1995. It is the core and most basic security function of the browser. Without the Same Origin policy, the browser is vulnerable to XSS and CSFR attacks. Same-origin means that the protocol, domain name, and port are the same. Even if two different domain names point to the same IP address, they are not same-origin.

Here’s an example:

const url = 'https://www.google.com:3000'
Copy the code

For example, the URL above, the protocol is HTTPS, the domain name is www.google.com, and the port is 3000.

What happens when you have a different source? There are many restrictions, such as:

  1. Cookie, LocalStorage, IndexDB and other stored content cannot be read
  2. The DOM node cannot be accessed
  3. The Ajax request goes out, but the response is intercepted by the browser

Without the same strategy, you might encounter:

  1. Cookie hijacking, where malicious websites steal data
  2. More vulnerable to XSS, CSRF attacks
  3. Unable to isolate potentially malicious files

Because of the browser’s same-origin policy, it’s possible that your Ajax request will be intercepted as soon as it’s sent, with an error:

- Access to XMLHttpRequest at'xxx' from origin 'xxx' has been block by CORS,
  policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Copy the code

Common cross-domain scenarios

A practical example across domains

Seeing is believing. It’s better to try it yourself.

Below we on Baidu prompt word interface to achieve an intelligent search, see what problems will be encountered.

Interface address:

  • Key words & cb = https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd= callback function

Interface parameters:

  • Wd: Keywords
  • Cb: indicates the name of the callback function

Return data format:

  • The json format

It’s a simple excuse, we just need to focus on keywords and callback functions. Similar to the home page of Baidu, we simply make an input box and a button on the page. When users input content and click the button, relevant requests will be triggered, and the returned data will be displayed on the page after the request is successful. The specific code is as follows:

<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Cross-domain Demo</title>
</head>

<body>
  <h1>Baidu prompt words</h1>
  <input type="text" id="keyword" placeholder="Please enter your keywords" />
  <input type="button" id="btn" onclick="clickBtn()" value="Query" />
  <div class="box"></div>

  <script>
    /** * button click event */
    function clickBtn() {
      // Get the contents of the hand input box
      let keyWord = document.getElementById("keyword").value;
      // If the input is empty, return directly
      if(! keyWord)return
      // Declare the request path
      let url = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + keyWord + "&cb=addList"
      // Implement the HTTP request
      let httpRequest = new XMLHttpRequest();// Step 1: Create the desired object
      httpRequest.open('GET', url, true);// Step 2: Open the connection
      httpRequest.send();// Step 3: Send the request
    };

    /** * The retrieved data callback displays the list of retrieved data in the element with the id box */
    function addList(data) {
      let list = "<ul>";
      for (let i = 0; i < data.s.length; i++) {
        let temp = data.s[i];
        list += "<li>" + temp + "</li>";
      }
      list += "</ul>";
      let box = document.querySelector(".box");
      box.innerHTML = list;
    };
  </script>
</body>

</html>
Copy the code

Ok, the page is ready, and let’s test it:

As expected, the interface crossed domains and the request failed

So how do we solve this?

Cross-domain solutions

Overview of Solutions

  1. Cross domains via JSONP
  2. Document.domain + iframe cross domain
  3. location.hash + iframe
  4. Window. name + iframe cross domain
  5. PostMessage cross-domain
  6. Cross-domain Resource Sharing (CORS)
  7. Nginx agents cross domains
  8. Nodejs middleware proxies cross domains
  9. The WebSocket protocol is cross-domain

First, cross domains through JSONP

Usually, in order to reduce the load of the Web server, we separate static resources such as JS, CSS and IMG to another server with an independent domain name, and then load static resources from different domain names in the HTML page through corresponding tags, which are allowed by the browser. Based on this principle, we can dynamically create script. Request a reference url to achieve cross-domain communication.

Native implementation:

 <script>
    var script = document.createElement('script');
    script.type = 'text/javascript';

    // Pass the name of a callback function to the back end, so that the back end can execute the callback function defined in the front end when it returns
    script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
    document.head.appendChild(script);

    // The callback executes the function
    function handleCallback(res) {
        alert(JSON.stringify(res));
    }
 </script>
Copy the code

The server returns the following (executes the global function when it returns) :

handleCallback({"status": true."user": "admin"})
Copy the code

The jquery ajax:

$.ajax({
    url: 'http://www.domain2.com:8080/login'.type: 'get'.dataType: 'jsonp'.// The request is jSONP
    jsonpCallback: "handleCallback".// Custom callback function name
    data: {}});Copy the code

Vue. Js:

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

Copy the code

Examples of back-end Node.js code:

var querystring = require('querystring');
var http = require('http');
var server = http.createServer();

server.on('request'.function(req, res) {
    var params = qs.parse(req.url.split('? ') [1]);
    var fn = params.callback;

    // jsonp returns Settings
    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... ');


Copy the code

Using JSONP in our page implementation as follows:

  /** * button click event */
  function clickBtn() {
    // Get the contents of the hand input box
    let keyWord = document.getElementById("keyword").value;
    // If the input is empty, return directly
    if(! keyWord)return
    // Declare the request path
    let url = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=" + keyWord + "&cb=addList"
    
    // Implement jSONP cross-domain requests
    let script = document.createElement("script");
    script.src = url
    let head = document.querySelector("head");
    head.appendChild(script);
  };
Copy the code

Disadvantages of JSONP: Only one get request can be implemented.

Document.domain + iframe cross domain

This solution applies only to cross-domain scenarios where the primary domain is the same and the subdomains are different.

Implementation principle: two pages through JS forced document.domain as the base of the primary domain, to achieve the same domain.

1.) parent window :(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>
Copy the code

2.) sub-window :(child.domain.com/b.html)

<script>
    document.domain = 'domain.com';
    // Get the variables in the parent window
    alert('get js data from parent ---> ' + window.parent.user);
</script>
Copy the code

Location. hash + iframe cross-domain

Implementation principle: A wants to communicate with B across domains, which is achieved through the middle page C. Three pages, different fields use iframe location.hash to transfer values, the same fields directly js access to communicate.

A domain: A.html -> B domain: B.html -> A domain: C.HTML, A and B different domain can only hash value one-way communication, B and C are also different domain can only one-way communication, but C and A are the same domain, so C can access all objects on A page through parent. Parent.

  1. A.h HTML: (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');

    // Pass hash values to B.html
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // callback methods open to homologous C.HTML
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>
Copy the code
  1. B.h HTML: (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');

    // listen for hash values from A.html and pass them to C.HTML
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>
Copy the code
  1. C. the TML: (www.domain1.com/c.html)
<script>
    // Listen for hash values from B.html
    window.onhashchange = function () {
        // Return the result by manipulating the javascript callback of the same domain A.html
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user='.' '));
    };
</script>
Copy the code

Window. Name + iframe cross domain

The window.name attribute is unique in that the name value persists across different pages (and even different domain names) and supports very long name values (2MB).

  1. A.h HTML: (www.domain1.com/a.html) –
var proxy = function(url, callback) {
    var state = 0;
    var iframe = document.createElement('iframe');

    // Load the cross-domain page
    iframe.src = url;

    // The onload event fires twice, the first time the cross-domain page is loaded and the data is stored in window.name
    iframe.onload = function() {
        if (state === 1) {
            // After the second onload(syndomain proxy page) succeeds, the data in syndomain window.name is read
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // After the first onload succeeds, switch to the same-domain proxy page
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1; }};document.body.appendChild(iframe);

    // After the data is retrieved, the iframe is destroyed to free memory; This also ensures security (not accessed by other fields frame JS)
    function destoryFrame() {
        iframe.contentWindow.document.write(' ');
        iframe.contentWindow.close();
        document.body.removeChild(iframe); }};// Request cross-domain B page data
proxy('http://www.domain2.com/b.html'.function(data){
    alert(data);
});
Copy the code
  1. Proxy. HTML: www.domain1.com/proxy…

Intermediate proxy page, same domain as A.HTML, content is empty.

  1. B.h HTML: (www.domain2.com/b.html) –
<script>
    window.name = 'This is domain2 data! ';
</script>
Copy the code

Conclusion:

The SRC attribute of iframe is used to transfer the cross-domain data from the outfield to the local region. In this case, the window.name of iframe is used to transfer the cross-domain data from the outfield to the local region. This is a neat way to circumvent the browser’s cross-domain access restrictions, but it’s also a secure operation.

PostMessage is cross-domain

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

  • A. Data transfer of the page and the new window it opens
  • B. Message transfer between multiple Windows
  • C. Page and nested IFrame message passing
  • D. Cross-domain data transfer in the above three scenarios

Usage: The postMessage(data, Origin) method takes two arguments data: the HTML5 specification supports any primitive type or replicable object, but some browsers only support strings, so it’s best to serialize the argument with json.stringify (). Origin: protocol + host + port number. The value can also be set to “*”, which indicates that it can be sent to any window. If you want to specify the origin of the current window, set it to “/”.

  1. A.h HTML: (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'
        };
        // Send cross-domain data to domain2
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };

    // Accept data from domain2
    window.addEventListener('message'.function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>
Copy the code
  1. B.h HTML: (www.domain2.com/b.html) –
<script>
    // Receive data from domain1
    window.addEventListener('message'.function(e) {
        alert('data from domain1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // Send it back to domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com'); }},false);
</script>
Copy the code

Vi. Cross-domain Resource Sharing (CORS)

Common cross-domain request: Only access-Control-allow-Origin can be set on the server. To request with cookie: both ends need to be set.

Note that due to the restriction of the same-origin policy, the cookie read is the cookie of the domain where the cross-domain request interface resides, not the current page. Nginx reverse proxy sets proxy_cookie_domain and cookieDomainRewrite (NodeJs).

Currently, all browsers support this functionality (IE8+ : IE8/9 requires the use of XDomainRequest objects to support CORS), and CORS has become a mainstream cross-domain solution.

Front-end Settings:

  1. Native ajax
// Set whether cookies are included in the front end
xhr.withCredentials = true;
Copy the code

Sample code:

var xhr = new XMLHttpRequest(); // Ie8/9 must be window.xdomainRequest compatible

// Set whether cookies are included in the front end
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); }};Copy the code
  1. jQuery ajax
$.ajax({
    ...
   xhrFields: {
       withCredentials: true    // Set whether cookies are included in the front end
   },
   crossDomain: true.// The request header contains additional cross-domain information, but does not contain cookies. });Copy the code
  1. Vue framework
  • Axios Settings:
axios.defaults.withCredentials = true
Copy the code
  • Vue – resource Settings:
Vue.http.options.credentials = true
Copy the code

Server Settings:

If the Settings are successful, the console of the front-end browser will not display cross-domain error messages; otherwise, the Settings are not successful.

  1. Java background:
/ * * import packages: import javax.mail. Servlet. HTTP. HttpServletResponse; * Defined in interface parameters: HttpServletResponse Response */

// Domain name that can be accessed across domains: Write the full port (protocol + domain name + port) if there is no port, do not add '/' at the end of the port.
response.setHeader("Access-Control-Allow-Origin"."http://www.domain1.com"); 

// Allow front-end authentication cookie: After this parameter is enabled, the domain name cannot be '*'. You must specify a specific domain name. Otherwise, the browser will prompt you
response.setHeader("Access-Control-Allow-Credentials"."true"); 

// Two common custom headers that need to be set on the back end when OPTIONS precheck is prompted
response.setHeader("Access-Control-Allow-Headers"."Content-Type,X-Requested-With");
Copy the code
  1. Nodejs backend example:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request'.function(req, res) {
    var postData = ' ';

    // Data block received
    req.addListener('data'.function(chunk) {
        postData += chunk;
    });

    // Data is received
    req.addListener('end'.function() {
        postData = qs.parse(postData);

        // Cross-domain background Settings
        res.writeHead(200, {
            'Access-Control-Allow-Credentials': 'true'.// The backend allows sending cookies
            'Access-Control-Allow-Origin': 'http://www.domain1.com'.// Allowed domain (protocol + domain name + port)
            /* * Set the cookie to domain2 instead of domain1, because the backend cannot write cookies across domains (nginx reverse proxy can do this), * but as long as domain2 write cookie authentication once, All subsequent cross-domain interfaces can obtain cookies from domain2, so that all interfaces can cross-domain access */
            'Set-Cookie': 'l=a123456; Path=/; Domain=www.domain2.com; HttpOnly'  // HttpOnly prevents js from reading cookies
        });

        res.write(JSON.stringify(postData));
        res.end();
    });
});

server.listen('8080');
console.log('Server is running at port 8080... ');
Copy the code

Nginx proxies cross domains

1, nginx configuration to resolve iconfont cross-domain

Browser cross-domain access js, CSS, and img conventional static resources are the same-origin policy permission, but iconfont font file (eot | otf | the vera.ttf | woff | SVG) exception, at this time in nginx server to add the following configuration static resources.

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

2. Nginx reverse proxy interfaces cross domains

Cross-domain principle: The same Origin policy is a security policy of the browser, not a part of the HTTP protocol. The server invokes the HTTP interface only using THE HTTP protocol, and does not execute JS scripts. There is no need for the same origin policy, so there is no crossing problem.

Nginx configure a proxy server (domain name and domain1 the same, different port) as a jumper, reverse proxy access to domain2 interface, and can incidentally modify the cookie in the domain information, convenient for the current domain cookie writing, cross-domain login.

Nginx configuration:

#proxy server {listen 81; server_name www.domain1.com; location / { proxy_pass http://www.domain2.com:8080; # reverse proxy proxy_cookie_domain www.domain2.com www.domain1.com; # change cookie domain name index index.html index.htm; # When accessing Nignx with middleware proxy interface such as Webpack-dev-server, there is no browser participation, so there is no source restriction. Add_header access-Control-allow-origin http://www.domain1.com; * add_header access-control-allow-credentials true; * add_header access-control-allow-credentials true; }}Copy the code
  1. Examples of front-end code:
var xhr = new XMLHttpRequest();

// Front-end switch: whether the browser reads and writes cookies
xhr.withCredentials = true;

// Access the proxy server in nginx
xhr.open('get'.'http://www.domain1.com:81/?user=admin'.true);
xhr.send();
Copy the code
  1. Nodejs backend example:
var http = require('http');
var server = http.createServer();
var qs = require('querystring');

server.on('request'.function(req, res) {
    var params = qs.parse(req.url.substring(2));

    // Write cookies to the front desk
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456; Path=/; Domain=www.domain2.com; HttpOnly'   // HttpOnly: the script cannot read
    });

    res.write(JSON.stringify(params));
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080... ');
Copy the code

Nodejs middleware proxy across domains

Node middleware implements cross-domain proxy, which is roughly the same as nginx. It starts a proxy server to forward data. It can also modify the domain name in the cookie in the response header by setting the cookieDomainRewrite parameter to implement cookie writing in the current domain, facilitating interface login authentication.

1. Cross-domain of non-VUE framework (twice cross-domain)

Build a proxy server with Node + Express + HTTP-proxy-middleware.

  1. Examples of front-end code:
var xhr = new XMLHttpRequest();

// Front-end switch: whether the browser reads and writes cookies
xhr.withCredentials = true;

// Access the HTTP-proxy-Middleware proxy server
xhr.open('get'.'http://www.domain1.com:3000/login?user=admin'.true);
xhr.send();
Copy the code
  1. Middleware server:
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // Proxy cross-domain target interface
    target: 'http://www.domain2.com:8080'.changeOrigin: true.// Modify the response header information to cross-domain and allow cookies
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin'.'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials'.'true');
    },

    // Change the cookie domain name in the response information
    cookieDomainRewrite: 'www.domain1.com'  // The value can be false, indicating no change
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000... ');
Copy the code
  1. Nginx = nginx

2. Cross-domain of VUE framework (once cross-domain)

Cross domain using node + Webpack + webpack-dev-server agent interface. In the development environment, since the Vue rendering service and the interface proxy service are the same as Webpack-dev-server, there is no need to set headers cross-domain information between the page and proxy interface.

Webpack.config.js part configuration:

module.exports = {
    entry: {},
    module: {},...devServer: {
        historyApiFallback: true.proxy: [{
            context: '/login'.target: 'http://www.domain2.com:8080'.// Proxy cross-domain target interface
            changeOrigin: true.secure: false.// Used when the agent reports an error with some HTTPS service
            cookieDomainRewrite: 'www.domain1.com'  // The value can be false, indicating no change}].noInfo: true}}Copy the code

9. WebSocket protocol is cross-domain

WebSocket Protocol is a new protocol for HTML5. It implements full duplex communication between browser and server, and allows cross-domain communication. It 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, 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');

// The connection was successfully processed
socket.on('connect'.function() {
    // Listen for server messages
    socket.on('message'.function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    // The listener server is closed
    socket.on('disconnect'.function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input') [0].onblur = function() {
    socket.send(this.value);
};
</script>
Copy the code
  1. Nodejs socket background
var http = require('http');
var socket = require('socket.io');

// Start the HTTP service
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... ');

// Listen for socket connections
socket.listen(server).on('connection'.function(client) {
    // Receive information
    client.on('message'.function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // Disconnect processing
    client.on('disconnect'.function() {
        console.log('Client socket has closed.'); 
    });
});
Copy the code

conclusion

Of course, in addition to the above scheme, there are other hacks: window.name, location.hash, but we do not recommend these cross-domain methods now, why? There are more secure and powerful PostMessage alternatives. In fact, there are many cross-domain solutions, summarized as follows:

  • CORS supports all HTTP requests and is the most popular solution across domains
  • JSONP only supports GET requests, but is compatible with older browsers
  • Node middleware and Nginx reverse proxies both take advantage of server-to-server same-origin policy restrictions
  • Websocket is also a cross-domain solution
  • PostMessage can be used for cross-document communication and more for window communication
  • Document.domain, window.name, and location.hash are dying out, and PostMessage is a good alternative

As a front-end novice, the purpose of this article is to record their learning experience, if there are shortcomings, please also give advice.

The road ahead is long, and I see no end.

Postscript: Hello friends, if you think this article is good, remember to give a thumbs-up or star, your thumbs-up and star is my motivation to write more and richer articles!Making the address



dbThe document library 由 dbusingCreative Commons Attribution – Non-commercial Use – Same way Share 4.0 International LicenseGrant permission.

Based on thegithub.com/danygitgitOn the creation of works.

Use rights other than those authorized by this License agreement may be obtained from
Creativecommons.org/licenses/by…Obtained.