First, what is cross-domain?

1. What is the same-origin policy and its limitations?

The same-origin policy is a convention. It is the core and most basic security function of the browser. If the same-origin policy is missing, the browser is vulnerable to attacks such as XSS and CSFR. The so-called homology means that the “protocol + domain name + port” is the same, even if two different domain names point to the same ip address, it is not homologous. The same-origin policy restrictions include:

  • Storage content such as cookies, LocalStorage, IndexedDB, etc.
  • DOM node
  • After the AJAX request was sent, the result was blocked by the browser.

But there are three tags that allow cross-domain loading of resources:

  • <img src=XXX>
  • <link href=XXX>
  • <script src=XXX>

2. Common cross-domain scenarios

When any one of the protocol, subdomain name, primary domain name, and port number is different, it is counted as a different domain . Requesting resources from different domains is considered as “cross-domain”. Common cross-domain scenarios are shown below:

Special two points:

First: If there is a cross-domain problem caused by protocols and ports, the “foreground” is powerless.

Second: On the cross-domain issue, it is only identified by the “URL header” and is not judged based on whether the IP address corresponding to the domain name is the same. “The first part of the URL” can be understood as “protocol, domain name and port must match” .

Here you may have a question: the request is cross-domain, then the request is sent out?

Cross-domain is not a request to send out, the request can be sent, the server can receive the request and return the result normally, but the result is intercepted by the browser . You may be wondering if a cross-domain request can be initiated through a form. Why is Ajax not? Because in the end, cross-domain is to prevent users from reading content under another domain name, Ajax can get a response, the browser thinks this is not Safe, so intercepted the response. But the form doesn’t get new content, so you can initiate a cross-domain request. It also shows that cross-domain does not completely block CSRF, because the request is sent out after all.

Second, cross-domain solutions

1.jsonp

1) Principle of JSONP

Using the <script>label does not restrict cross-domain vulnerabilities, web pages can get JSON data dynamically generated from other sources. JSONP requests must be supported by the other server.

2) Comparison of JSONP and AJAX

JSONP is the same as AJAX, which is a way for a client to send a request to the server to retrieve data from the server. But AJAX belongs to the same-origin policy, and JSONP belongs to non-homologous policy (cross-domain request).

3) Advantages and disadvantages of JSONP

The advantage of JSONP is that it is simple and compatible, and can be used to solve the problem of cross-domain data access of mainstream browsers. The disadvantage is that only the get method is limited, and it may be vulnerable to XSS attacks.

4) JSONP implementation process

  • Declare a callback function whose function name (such as show) is passed as a parameter value to the server that requests data across domains, and the function parameter is to get the target data (data returned by the server).
  • Create a <script>tag, assign the cross-domain API data interface address to the src of the script, and pass the function name to the server at this address (you can pass the question mark: ?callback=show).
  • After the server receives the request, it needs special processing: the name of the function passed in and the data it needs to be spliced ​​into a string. For example, the function name passed in is show, and the data it prepares is show('I reject').
  • Finally, the server returns the prepared data to the client through the HTTP protocol, and the client calls the callback function (show) declared before the execution, and operates on the returned data.

The name of the callback function that may encounter multiple JSONP requests in development is the same, so you need to wrap a JSONP function yourself.

// index.html
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    let script = document.createElement('script')
    window[callback] = function(data) {
      resolve(data)
      document.body.removeChild(script)
    }
    params = { ...params, callback } // wd=b&callback=show
    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/say',
  params: { wd: 'I accept' },
  callback: 'show'
}).then(data => {
  console.log(data)
})

The above code is equivalent to http://localhost:3000/say?wd=Iloveyou&callback=showrequesting data from this address, then returning show('I reject')in the background , and finally running the show() function to print out ‘I don’t love you’.

// server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res) {
  let { wd, callback } = req.query
  console.log(wd) // Iloveyou
  console.log(callback) // show
  res.end(`${callback}('I Reject')`)
})
app.listen(3000)

5) jQuery’s jsonp form

JSONP is a GET and asynchronous request. There are no other request methods and synchronization requests, and jQuery will clear the cache for JSONP requests by default.

$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",
jsonpCallback:"show",
jsonp:"callback",
success:function (data){
console.log(data);}
});

2.cors

CORS requires both browser and backend support. IE 8 and 9 need to be implemented with XDomainRequest .

The browser automatically performs CORS communication, and the key to implementing CORS communication is the back end. As long as the back end implements CORS, cross-domain is implemented.

The server can open CORS by setting Access-Control-Allow-Origin. This attribute indicates which domain names can access the resource. If a wildcard is set, all sites can access the resource.

Although setting CORS has nothing to do with the front end, if you solve the cross-domain problem in this way, there will be two situations when sending the request, which are simple request and complex request .

1) Simple request

As long as the following two conditions are met at the same time, it is a simple request.

Condition 1: Use one of the following methods:

  • GET
  • HEAD
  • POST

Condition 2: The value of Content-Type is limited to one of the following three:

  • Text/plain
  • Multipart/form-data
  • Application/x-www-form-urlencoded

No XMLHttpRequestUpload object in the request is registered with any event listeners; the XMLHttpRequestUpload object can be accessed using the XMLHttpRequest.upload property.

2) Complex requests

A request that does not meet the above conditions is definitely a complicated request.
A CORS request for a complex request adds an HTTP query request, called a “preflight” request, before the formal communication. The request is an option method to know whether the server allows cross-domain requests.

When we use PUTthe request to the background, it is a complex request, and the background needs to be configured as follows:

// allowed
res.setHeader('Access-Control-Allow-Methods', 'PUT')
// alive time
res.setHeader('Access-Control-Max-Age', 6)
// OPTIONS no action
if (req.method === 'OPTIONS') {
  res.end() 
}
//response
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.end('I Reject')
})

Next, let’s look at an example of a complete complex request and introduce the fields related to the CORS request.

// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen' // cookie no allowed
xhr.withCredentials = true // with cookie
xhr.open('PUT', 'http://localhost:4000/getData', true)
xhr.setRequestHeader('name', 'xiamen')
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.response)
      //Access-Control-Expose-Headers
      console.log(xhr.getResponseHeader('name'))
    }
  }
}
xhr.send()
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
//server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] 
app.use(function(req, res, next) {
  let origin = req.headers.origin
  if (whitList.includes(origin)) {
    //source allowed
    res.setHeader('Access-Control-Allow-Origin', origin)
    // head allowed
    res.setHeader('Access-Control-Allow-Headers', 'name')
    // method allowed
    res.setHeader('Access-Control-Allow-Methods', 'PUT')
    // cookie allowed
    res.setHeader('Access-Control-Allow-Credentials', true)
    //
    res.setHeader('Access-Control-Max-Age', 6)
    // head response
    res.setHeader('Access-Control-Expose-Headers', 'name')
    if (req.method === 'OPTIONS') {
      res.end() 
    }
  }
  next()
})
app.put('/getData', function(req, res) {
  console.log(req.headers)
  res.setHeader('name', 'jw') 
  res.end('I Reject')
})
app.get('/getData', function(req, res) {
  console.log(req.headers)
  res.end('I Reject')
})
app.use(express.static(__dirname))
app.listen(4000)

The code of http://localhost:3000/index.htmlthe http://localhost:4000/request across domains, as we said above, is the key to the rear end of CORS communication.

3.postMessage

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

  • Data transfer of the page and its opening new window
  • Message passing between multiple windows
  • Page with nested iframe messaging
  • Cross-domain data transfer for the above three scenarios

The postMessage() method allows scripts from different sources to communicate in a limited manner in an asynchronous manner, enabling cross-text files, multi-window, and cross-domain messaging .

otherWindow.postMessage(message, targetOrigin, [transfer]);

  • Message: The data to be sent to other windows.
  • targetOrigin: Use the origin attribute of the window to specify which windows can receive message events. The value can be either the string “*” (for unlimited) or a URI. When sending a message, if any of the target window’s protocol, host address, or port does not match the value provided by targetOrigin, the message will not be sent; only if the three match exactly, the message will be sent.
  • Transfer (optional): is a bunch of Transferable objects that are passed at the same time as the message. The ownership of these objects will be transferred to the recipient of the message, and the sending party will no longer retain ownership.

Next, let’s look at an example: the http://localhost:3000/a.htmlpage http://localhost:4000/b.htmlpasses “I accept” and then the latter returns “I reject.”

// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> 
  //in http://localhost:3000/a.html
    <script>
      function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('I accept', 'http://localhost:4000') 
        window.onmessage = function(e) { 
          console.log(e.data) 
        }
      }
    </script>
// b.html
  window.onmessage = function(e) {
    console.log(e.data) 
    e.source.postMessage('I reject', e.origin)
 }

4.websocket

Websocket is a persistent protocol for HTML5 that implements full-duplex communication between the browser and the server, and is also a cross-domain solution. Both WebSocket and HTTP are application layer protocols, all based on the TCP protocol. However, WebSocket is a two-way communication protocol. After establishing a connection, the server and client of WebSocket can send or receive data to the other party actively . At the same time, WebSocket needs to use the HTTP protocol when establishing a connection. After the connection is established, the two-way communication between the client and the server is independent of HTTP.

The native WebSocket API is not very convenient to use, we use Socket.ioit, it encapsulates the webSocket interface nicely, provides a simpler and more flexible interface, and provides backward compatibility for browsers that do not support webSocket.

Let’s look at an example: local file socket.html to localhost:3000generate data and accept data

// socket.html
<script>
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function () {
      socket.send('I accept');//send to server
    }
    socket.onmessage = function (e) {
      console.log(e.data);//receivce from server

    }
</script>
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
  ws.on('message', function (data) {
    console.log(data);
    ws.send('I Reject')
  });
})

5. Node middleware agent (twice cross-domain)

Implementation principle: The same-origin policy is the standard that the browser needs to follow, and if the server requests from the server, it does not need to follow the same-origin policy. 
Proxy server, you need to do the following steps:

  • Accept client requests.
  • Forward the request to the server.
  • Get the server response data.
  • Forward the response to the client.

Let’s look at an example: the local file index.html file, requesting data http://localhost:3000from the target server through the proxy server http://localhost:4000.

// index.html(http://127.0.0.1:5500)
 <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
    <script>
      $.ajax({
        url: 'http://localhost:3000',
        type: 'post',
        data: { name: 'xiamen', password: '123456' },
        contentType: 'application/json;charset=utf-8',
        success: function(result) {
          console.log(result) // {"title":"fontend","password":"123456"}
        },
        error: function(msg) {
          console.log(msg)
        }
      })
     </script>
// server1.js   proxy(http://localhost:3000)
const http = require('http')
// accpet request
const server = http.createServer((request, response) => {
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type'
  })
  // send to server
  const proxyRequest = http
    .request(
      {
        host: '127.0.0.1',
        port: 4000,
        url: '/',
        method: request.method,
        headers: request.headers
      },
      serverResponse => {
        // receive
        var body = ''
        serverResponse.on('data', chunk => {
          body += chunk
        })
        serverResponse.on('end', () => {
          console.log('The data is ' + body)
          //response
          response.end(body)
        })
      }
    )
    .end()
})
server.listen(3000, () => {
  console.log('The proxyServer is running at http://localhost:3000')
})
// server2.js(http://localhost:4000)
const http = require('http')
const data = { title: 'fontend', password: '123456' }
const server = http.createServer((request, response) => {
  if (request.url === '/') {
    response.end(JSON.stringify(data))
  }
})
server.listen(4000, () => {
  console.log('The server is running at http://localhost:4000')
})

The above code crosses the domain twice. It is worth noting that the browser sends the request to the proxy server, also follows the same-origin policy, and finally prints out in the index.html file.{"title":"fontend","password":"123456"}

6.nginx reverse proxy

The implementation principle is similar to the Node middleware proxy, which requires you to set up a transit nginx server for forwarding requests.

Using nginx reverse proxy to implement cross-domain is the simplest cross-domain approach. You only need to modify the configuration of nginx to solve cross-domain problems, support all browsers, support sessions, do not need to modify any code, and will not affect server performance.

Implementation idea: configure a proxy server (domain name is the same as domain1, port is different) through nginx, and the reverse proxy accesses the domain2 interface, and can modify the domain information in the cookie to facilitate the writing of the current domain cookie and realize cross-domain login.

First download nginx , then modify nginx.conf in the nginx directory as follows:

// proxy
server {
    listen       80;
    server_name  www.domain1.com;
    location / {
        proxy_pass   http://www.domain2.com:8080;  #revers proxy
        proxy_cookie_domain www.domain2.com www.domain1.com; 
        index  index.html index.htm;

        add_header Access-Control-Allow-Origin http://www.domain1.com;  
        add_header Access-Control-Allow-Credentials true;
    }
}

Finally, nginx -s reloadstart nginx from the command line .

// index.html
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('get', 'http://www.domain1.com:81/?user=admin', true);
xhr.send();
// server.js
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));
    // cookie
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly'   // HttpOnly:
    });
    res.write(JSON.stringify(params));
    res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');

7.window.name + iframe

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

Where a.html and b.html are in the same domain, both http://localhost:3000; and c.html ishttp://localhost:4000

 // a.html(http://localhost:3000/b.html)
  <iframe src="http://localhost:4000/c.html" frameborder="0" onload="load()" id="iframe"></iframe>
  <script>
    let first = true
    // onload trigger two times,first load cross page and save data window.name
    function load() {
      if(first){
      //switch to proxy
        let iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{
      // read window.name
        console.log(iframe.contentWindow.name);
      }
    }
  </script>

B.html is an intermediate proxy page, with the same domain as a.html, the content is empty.

 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = 'I reject'  
  </script>

Summary: Through the iframe’s src attribute from the outer domain to the local domain, the cross-domain data is passed from the outer domain to the local domain by the iframe’s window.name. This subtly bypasses the browser’s cross-domain access restrictions, but at the same time it is safe to operate.

8.location.hash + iframe

Implementation principle: a.html wants to communicate with c.html cross-domain, through the intermediate page b.html to achieve. Three pages, using the iframe’s location.hash value between different domains, direct js access between the same domain to communicate.

Specific implementation steps: At the beginning a.html sends a hash value to c.html, then c.html receives the hash value, then passes the hash value to b.html, and finally b.html puts the result to a.html In the hash value.
Similarly, a.html and b.html are in the same domain, both http://localhost:3000; and c.html ishttp://localhost:4000

// a.html
  <iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
  <script>
    window.onhashchange = function () { //hash
      console.log(location.hash);
    }
  </script>
// b.html
  <script>
    window.parent.parent.location.hash = location.hash 
    //b.html result in a.html hash,b.html  access by parent.parent
  </script>
 // c.html
 console.log(location.hash);
  let iframe = document.createElement('iframe');
  iframe.src = 'http://localhost:3000/b.html#idontloveyou';
  document.body.appendChild(iframe);

9.document.domain + iframe

The case of this embodiment can only be used for the same two domains, such a.test.comand b.test.comsuitable for the embodiment .
Just give this page document.domain ='test.com'represent two domain names are the same can be achieved across domains.

Implementation principle: Both pages are forced to set document.domain as the basic primary domain through js, and the same domain is implemented.

Let’s look at an example: the page a.zf1.cn:3000/a.htmlgets b.zf1.cn:3000/b.htmlthe value of a in the page .

// a.html
<body>
 helloa
  <iframe src="http://b.zf1.cn:3000/b.html" frameborder="0" onload="load()" id="frame"></iframe>
  <script>
    document.domain = 'zf1.cn'
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>
// b.html
<body>
   hellob
   <script>
     document.domain = 'zf1.cn'
     var a = 100;
   </script>
</body>

Third, summary

  • CORS supports all types of HTTP requests and is the fundamental solution for cross-domain HTTP requests.
  • JSONP only supports GET requests. The advantage of JSONP is that it supports older browsers and can request data from websites that do not support CORS.
  • Regardless of whether it is a Node middleware proxy or a nginx reverse proxy, the server is not restricted by the same-origin policy.
  • In daily work, the more cross-domain schemes used are cors and nginx reverse proxy.