What is cross-domain

Cross-domain is when a document or script in one domain tries to request a resource in another domain. Typically we are talking about cross-domain request scenarios that are restricted by the browser’s same-origin policy.

The same-origin policy

Same-origin indicates that the protocol, domain name, and port of two urls are the same. Even if two domain names point to the same IP address, they are not same-origin.

SOP (Same Origin Policy) is a basic set of security policy constraints for browsers that restrict how documents from one Origin or the scripts it loads can interact with resources from another source. 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.

The same-origin policy is mainly manifested in DOM, Web data, and network.

DOM level: The same-origin policy limits how JavaScript scripts from different sources can read and write to the current DOM object.

Data level: The same-origin policy prevents sites from accessing Cookie, IndexDB, and LocalStorage data of the current site.

Network level: The same origin policy restricts the sending of a site’s data to a site from a different source using methods such as XMLHttpRequest.

Welcome to follow my wechat official account: FrontGeek

Cross-domain solutions

There are nine main solutions:

  • JSONP
  • CORS(Cross-domain resource sharing, most commonly used)
  • postMessage + iframe
  • document.domain + iframe
  • window.name + iframe
  • location.hash + iframe
  • WebSocket
  • Nginx agents cross domains
  • Nodejs middleware proxies cross domains

JSONP

Browsers only have same-origin request restrictions for XMLHttpRequest requests, but not for script tag SRC attributes, link tag ref attributes, and IMG tag SRC attributes. This “vulnerability” is a good way to resolve cross-domain requests. JSONP takes advantage of the fact that script tags have no homology constraints. Of course, the backend server needs to cooperate, return a legitimate JS script, generally is a call JS function statement, data as the function’s entry parameter.

Let’s use the following example to briefly show how to resolve cross-domains with JSONP.

const express = require('express');
const app = express();

app.get('/jsonp'.(req, res) = > {
  let {wd, cb} = req.query;
  console.log(wd, cb);
  res.end(`${cb}(' interface returns test data ') ');
})

app.listen(3000.() = > {
  console.log('app listening on port 3000');
})
Copy the code
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Jsonp addresses cross domains</title>
</head>
<body>
  <script>
    function jsonp({url, params, cb}) {
      return new Promise((resolve, reject) = > {
        let script = document.createElement('script');
        window[cb] = function(data) {
          resolve(data)
          document.body.removeChild(script); } params = {... params, cb};let arrs = [];
        for (let key in params) {
          arrs.push(`${key}=${params[key]}`)
        }
        script.src = `${url}?${arrs.join('&')}`;
        document.body.appendChild(script);
      })
    }
    jsonp({
      url: 'http://localhost:3000/jsonp'.params: {
        wd: 'b'
      },
      cb: 'show' // The callback function name
    }).then(data= > {
      console.log(data)
    })
  </script>
</body>
</html>
Copy the code

JSONP has the following disadvantages:

  • Only GET requests are supported, but other types of HTTP requests such as POST are not
  • Supporting only cross-domain HTTP requests does not solve the problem of how to make JavaScript calls between two pages in different domains.
  • JSONP does not return various HTTP status codes when a call fails.
  • Not safe. In case a service providing JSONP has a page injection vulnerability, it is vulnerable to XSS attacks.

CORS(Cross-domain resource sharing, most commonly used)

Cross-origin Resource Sharing (CORS) is an HTTP header based mechanism that allows a server to identify other origins (domains, protocols, and ports) besides its own, so that browsers can access and load these resources.

The browser will automatically carry out CORS communication, the key to achieve CORS communication is the back end. As long as the backend implements CORS, cross-domain is achieved.

Let’s look at the use of CORS with a simple example:

const express = require('express');
const app = express();

let whiteList = ['http://localhost:3000']

app.use(function (req, res, next) {
  console.log(req.headers);
  let origin = req.headers.origin;
  if (whiteList.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin); // Accept the request for origin
    res.setHeader('Access-Control-Allow-Headers'.'x-name'); // Indicates all header fields supported by the server
  }
  next();
})

app.get('/getData'.(req, res) = > {
  res.end('Interface returns test data');
})

app.use(express.static(__dirname));

app.listen(4000)
Copy the code

We by opening the HTML below: http://localhost:3000/index.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>CORS</title>
</head>
<body>
  <p>cors test html</p>

  <script>
    let xhr = new XMLHttpRequest();
    xhr.open('GET'.'http://localhost:4000/getData'.true)
    xhr.setRequestHeader('x-name'.'test')
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response)
        }
      }
    }
    xhr.send();
  </script>
</body>
</html>
Copy the code

In this method, the back-end service interface sets the corresponding correct CORS response header in the response message. This is also the most commonly used method to solve cross-domain problems.

More details can be viewed nguyen other teacher’s article: www.ruanyifeng.com/blog/2016/0…

postMessage + iframe

PostMessage is an API introduced in H5 that allows scripts from different sources to communicate asynchronously in a limited manner, enabling cross-text file, multi-window, cross-domain messaging.

We have two services, A.HTML on http://localhost:3000 and B.HTML on http://localhost:4000.

The two HTML codes are as follows:

a.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <p>a page</p>
  <span id="message"></span>
  <iframe src="http://localhost:4000/b.html" frameborder="1" id="frame"></iframe>

  <script>
    window.onload = function() {
      let frame = document.getElementById('frame');
      frame.contentWindow.postMessage('Test message'.'http://localhost:4000/b.html')}window.onmessage = function (e) {
      // Accept the message
      document.getElementById('message').innerHTML = ` received${e.origin}The message:${e.data}`;
    }
  </script>
</body>
</html>
Copy the code

b.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <p>b page</p>
  <span id="message"></span>

  <script>
    window.onmessage = function (e) {
      // Accept the message
      console.log('b page onmessage', e.data);
      document.getElementById('message').innerHTML = ` received${e.origin}The message:${e.data}`;
      top.postMessage('B page received message'.'http://localhost:3000/a.html');
    }
  </script>
</body>
</html>
Copy the code

window.name

When the page is displayed in the browser, we can get the global variable window. The window variable has a name attribute, which has the following characteristics:

  • Each window has a separate window.name corresponding to it
  • During the life of a window (before it is closed), all pages loaded by the window share a window.name, and each page has read and write permissions to window.name.
  • Window.name is always present in the current window, and does not change the window.name value even if a new page is loaded.
  • Window. name can store a maximum of 2 MB of data in a customized data format

We prepare three pages: A.HTML and B.HTML on http://localhost:3000 and C.HTML on http://localhost:4000.

Target: To get the data sent by PAGE C on page A

C puts the value in window.name and changes the reference address of A to B

The code is as follows: A.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <p>a page</p>
  <iframe src="http://localhost:4000/c.html" frameborder="0" id="frame" onload="load()"></iframe>

  <script>
    let first = true;
    function load() {
      if (first) {
        let frame = document.getElementById('frame');
        frame.src = 'http://localhost:3000/b.html';
        first = false;
      } else {
        console.log(frame.contentWindow.name); }}</script>
</body>
</html>
Copy the code

b.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <p>b page</p>
</body>
</html>
Copy the code

c.html

<! DOCTYPEhtml>
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script>
    window.name = 'window.name implementation across domains'
  </script>
</body>
</html>
Copy the code

location.hash

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

We prepare three pages: A.HTML and B.HTML on http://localhost:3000 and C.HTML on http://localhost:4000.

Objective: To obtain the data sent by PAGE C on page A.

After C receives the hash value, C passes the hash value to B, and B puts the result into A’s hash value

The code is as follows:

a.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <iframe src="http://localhost:4000/c.html#testData" frameborder="0" id="frame" onload="load()"></iframe>

  <script>
    window.onhashchange = function() {
      console.log(location.hash);
    }
  </script>
</body>
</html>
Copy the code

b.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script>
    window.parent.parent.location.hash = location.hash
  </script>
</body>
</html>
Copy the code

c.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script>
    console.log(location.hash);
    let iframe = document.createElement('iframe');
    iframe.src = 'http://localhost:3000/b.html#cPageToBData';
  </script>
</body>
</html>
Copy the code

document.domain

This mode can be used only when the secondary domain names are the same. For example, a.test.com and b.test.com are used in this mode.

Just add document.domain =’test.com’ to the page to indicate that the secondary domain is the same.

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

a.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <iframe src="http://b.test.com/b.html" frameborder="0" id="frame" onload="load()"></iframe>

  <script>
    document.domain = 'test.com'
    function load() {
      console.log(frame.contentWindow.data);
    }
  </script>
</body>
</html>
Copy the code

b.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script>
    document.domain = 'test.com';
    var data = 'Here's the data from page B.';
  </script>
</body>
</html>
Copy the code

WebSocket

WebSocket is a browser API whose goal is to provide full-duplex, two-way communication over a single persistent connection.

WebSocket is a two-way communication protocol. After a connection is established, both the WebSocket server and client can actively send or receive data to each other. At the same time, the WebSocket needs to use HTTP protocol to establish a connection. After the connection is established, the two-way communication between the client and server is independent of HTTP.

The same origin policy does not apply to WebSocket.

Let’s look at a simple example: local socket.html sends and receives data to localhost:3000:

// socket.html
<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>Document</title>
</head>
<body>
  <script>
    // Advanced API is not compatible with socket. IO library
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function() {
      socket.send('test data');
    }
    socket.onmessage = function(e) {
      console.log(e.data);
    }
  </script>
</body>
</html>
Copy the code
// server.js
let express = require('express');
let app = express();

let WebSocket = require('ws');
let wss = new WebSocket.Server({port:3000})
wss.on('connection'.function(ws) {
  ws.on('message'.function(data) {
    console.log(data);
    ws.send('response data'); })})Copy the code

Nodejs middleware proxies cross domains

The same origin policy applies to browsers. If the server requests requests from the server, the same origin policy is not required. Nodejs middleware agent cross-domain uses this principle to send cross-domain request to proxy server, and the proxy server forwards the request.

The proxy server needs to do the following steps:

  • Accept client requests
  • Forwards the request to the server
  • Get the server response data
  • The response is forwarded to the client

Localhost :3000: proxy server localhost:4000

index.html

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
  <title>http proxy</title>
</head>
<body>
  <p>http://localhost:3000/index.html</p>

  <script>
    let xhr = new XMLHttpRequest();
    xhr.open('GET'.'http://localhost:3000/getData'.true)
    xhr.setRequestHeader('x-name'.'test')
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
          console.log(xhr.response)
        }
      }
    }
    xhr.send();
  </script>
</body>
</html>
Copy the code

proxyServer.js

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

/ / use Express hosting static files, can be accessed through http://localhost:3000/index.html index. The HTML, achieve cross-domain
app.use(express.static(__dirname));

// Proxy server operation
// Set to allow cross-domain access to the service
app.all(The '*'.(req, res, next) = > {
  res.header('Access-Control-Allow-Origin'.The '*');
  res.header('Access-Control-Allow-Headers'.'Content-Type');
  res.header('Access-Control-Allow-Methods'.The '*');
  res.header('Content-Type'.'application/json; charset=utf-8');
  next();
});

// http-proxy-middleware
// The middleware forwards each request to the http://localhost:3001 backend server
app.use('/', createProxyMiddleware({target: 'http://localhost:4000'.changeOrigin: true}))

app.listen(3000)
Copy the code

server.js

const express = require('express');
const app = express();

app.get('/getData'.(req, res) = > {
  res.end('NodeJS middleware proxy returns data across domains');
})

app.use(express.static(__dirname));

app.listen(4000)
Copy the code

nginx

The implementation principle is similar to Node middleware proxy, requiring you to build a nginx server for forwarding requests.

Nginx reverse proxy is the simplest way to cross domains. Nginx only needs to change the configuration to solve the cross-domain problem, support all browsers, support sessions, no code changes, and do not affect server performance.

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.

The following is a simple example of nginx configuration:

// Proxy server
server {
    listen       81;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080; # Reverse proxyproxy_cookie_domain www.domain2.com www.domain1.com; # change cookie domain name index index.html index.htm; Add_header access-Control-allow-origin HTTP: add_header access-Control-allow-origin HTTP: add_header access-Control-allow-origin HTTP: add_header access-Control-allow-origin HTTP: add_header access-Control-allow-origin//www.domain1.com; # If the current end is cross-domain only without cookies, the value can be *
        add_header Access-Control-Allow-Credentials true; }}Copy the code

Example code for this article: cross-domain

The resources

www.jianshu.com/p/e1e2920da… www.ruanyifeng.com/blog/2016/0…

If you find this helpful:

1. Click “like” to support it, so that more people can see this article

2, pay attention to the public account: FrontGeek technology (FrontGeek), we learn and progress together.