preface

Front and back end data interaction often encounter request cross-domain, what is cross-domain, and what are the types of cross-domain, this article will discuss.

Please poke the complete source code for this articleMaking a blogOn paper come zhongjue shallow, I suggest you knock on the code.

I. What is cross-domain?

1. What is the same origin policy and what it restricts?

The same origin policy is a convention. It is the most core and basic security function of browsers. Without the same origin policy, browsers are vulnerable to XSS, CSRF, and other 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.

The contents of the same-origin policy are as follows:

  • Storage content such as Cookie, LocalStorage, and IndexedDB
  • DOM node
  • After the AJAX request is sent, the result is intercepted by the browser

But there are three tags that allow loading resources across domains:

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

2. Common cross-domain scenarios

If the protocol, subdomain name, primary domain name, or port number are different, they are counted as different domains. When domains request resources from each other, they are considered “cross-domains.” The following figure shows a common cross-domain scenario:

Two points in particular:

First: if the protocol and port caused cross-domain problems “front desk” is powerless.

Second: in the cross-domain problem, only through the “URL header” to identify not according to the domain name corresponding to the IP address is the same to judge. “URL header” can be interpreted as “Protocol, domain name and port must match”.

Here you may have a question: the request is cross-domain, so did the request ever go out?

Cross-domain does not mean that the request cannot be sent 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 you can make cross-domain requests with forms, why not with Ajax? Because, ultimately, cross-domain is about preventing the user from reading something under another domain, Ajax can get the response, and the browser thinks it’s unsafe, so it intercepts the response. However, the form does not get new content, so cross-domain requests can be made. It also shows that cross-domain does not completely block CSRF because the request is sent after all.

2. Cross-domain solutions

1.jsonp

1) the principle of the json

using<script>Tags do not have the vulnerability of cross-domain restrictions, and web pages can get dynamically generated JSON data from other sources. JSONP requests must be supported by the server on the other side.

2) JSONP vs. AJAX

JSONP, like AJAX, is a way for the client to send a request to the server and get data from the server. While AJAX is the same origin policy, JSONP is the non-same origin policy (cross-domain requests)

3) Advantages and disadvantages of JSONP

JSONP has the advantage of being simple and compatible and can be used to solve cross-domain data access problems in major browsers. The disadvantage is that only support for the GET method has limitations and is insecure and may be subjected to XSS attacks.

4) Implementation process of JSONP

  • Declare a callback function with the function name (such as show) as the parameter value to be passed to the server requesting cross-domain data, and the function parameter to get the target data (the data returned by the server).
  • To create a<script>Tag, assign the address of the cross-domain API data interface to the SRC of the script, and pass the function name to the server at that address. The callback = show).
  • When the server receives the request, it needs to do something special: concatenate the name of the function passed in with the data it needs to give you into a string. For example, the name of the function passed in is show, and the data it prepares isShow (' I don't love you ').
  • Finally, the server returns the prepared data to the client through HTTP protocol. The client then calls the callback function (show) declared before to operate on the returned data.

In development, you may encounter multiple JSONP requests with the same callback function name, and you may need to encapsulate 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=showlet 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: 'Iloveyou' },
  callback: 'show'
}).then(data => {
  console.log(data)
})
Copy the code

The above code to http://localhost:3000/say? Wd =Iloveyou&callback=show this address requests data, and then returns show(‘ I don’t loveyou ‘) in the background. Finally, it runs the function show() and prints out ‘I don’t loveyou’.

// 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 don't love you')`)
})
app.listen(3000)
Copy the code

5) JSONp form of jQuery

JSONP is GET and asynchronous, there is no other way to request or synchronize requests, and jQuery clears the cache for JSONP requests by default.

$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp".type:"get"// You can omit jsonpCallback:"show",//-> customize the name of the function passed to the server, instead of using jQuery to generate it automatically, omit jsonp:"callback"//-> pass the function name to the callback parameter, omit success:function (data){
console.log(data);}
});
Copy the code

2.cors

CORS requires both browser and back-end support. IE 8 and 9 require XDomainRequest to implement.

The browser will automatically carry out CORS communication, the key to realize CORS communication is the back end. As long as the CORS are implemented at the back end, cross-domain is implemented.

Set access-control-allow-Origin on the server to enable CORS. This property indicates which domain names can access the resource. If the wildcard character is set, all websites can access the resource.

While setting up CORS has nothing to do with the front end, solving cross-domain problems in this way can lead to two situations when sending requests, simple and complex.

1) Simple requests

As long as the following two conditions are met, 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:

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

None of the XMLHttpRequestUpload objects in the request register 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 criteria is definitely a complex request. CORS requests for complex requests add an HTTP query request, called a “precheck” request, before formal communication. This request is an option method to know whether the server will allow cross-domain requests.

When we use PUT to request the background, it is a complex request, and the background needs to do the following configuration:

// Which method is allowed to access me res.setheader ('Access-Control-Allow-Methods'.'PUT') // Precheck survival time res.setheader ('Access-Control-Max-Age', 6) // OPTIONS requests are not processedif (req.method === 'OPTIONS') {res.end()} // Define the content returned in the background app.put()'/getData'.function(req, res) {
  console.log(req.headers)
  res.end('I don't love you')})Copy the code

Let’s look at the next example of a complete complex request and introduce the fields associated with CORS requests

// index.html
let xhr = new XMLHttpRequest()
document.cookie = 'name=xiamen'// Cookies cannot cross domains xhr.withCredentials =true// The front end sets whether to include cookies 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) {the console. The log (XHR. Response) / / get the response headers, Access-control-exposure-headers console.log(xhr.getresponseHeader)'name'))
    }
  }
}
xhr.send()
Copy the code
//server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
Copy the code
//server2.js
let express = require('express')
let app = express()
let whitList = ['http://localhost:3000'] // Set the whitelist app.use(function(req, res, next) {
  let origin = req.headers.origin
  if(whitlist.includes (origin)) {// set which source can access my res.setheader ('Access-Control-Allow-Origin'// Which header is allowed to access me res.setheader ('Access-Control-Allow-Headers'.'name') // Which method is allowed to access me res.setheader ('Access-Control-Allow-Methods'.'PUT') // Allow cookie res.setheader ('Access-Control-Allow-Credentials'.true) // Precheck survival time res.setheader ('Access-Control-Max-Age', 6) // Allow the return header res.setheader ('Access-Control-Expose-Headers'.'name')
    if (req.method === 'OPTIONS') {res.end() // OPTIONS requests do nothing}} next()}) app.put()'/getData'.function(req, res) {
  console.log(req.headers)
  res.setHeader('name'.'jw'// Return a response header with res.end() set in the background'I don't love you')
})
app.get('/getData'.function(req, res) {
  console.log(req.headers)
  res.end('I don't love you')
})
app.use(express.static(__dirname))
app.listen(4000)
Copy the code

The above code from http://localhost:3000/index.html to http://localhost:4000/ cross-domain request, as we mentioned above, the backend is the key to the realization 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 operated across domains. It can be used to solve the following problems:

  • Data transfer between the page and the new window it opens
  • Messaging between multiple Windows
  • Page with nested IFrame message delivery
  • Cross-domain data transfer for the three scenarios above

The postMessage() method allows limited communication between scripts from different sources in an asynchronous manner, enabling cross-text file, multi-window, cross-domain messaging.

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

  • Message: Data to be sent to another window.
  • TargetOrigin: Specifies which Windows can receive message events through the origin attribute of the window. The value can be a string “*” (for unlimited) or a URI. If the protocol, host address, or port of the target window does not match the value provided by targetOrigin, the message will not be sent. The message will only be sent if the three match exactly.
  • Transfer (optional) : is a string of Transferable objects that are transferred at the same time as message. Ownership of these objects will be transferred to the recipient of the message, and the sender will no longer retain ownership.

Let’s look at an example: http://localhost:3000/a.html page to http://localhost:4000/b.html “I love you”, then the latter back. “I do not love you.”

// a.html
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"> < iframe > / / finished loading it triggers an event / / embedded in http://localhost:3000/a.html < script >function load() {
        let frame = document.getElementById('frame')
        frame.contentWindow.postMessage('I love you'.'http://localhost:4000') // Send data window.onmessage =function(e) {// accept return data console.log(e.ata) // I don't love you}} </script>Copy the code
// b.html
  window.onmessage = function(e) {console.log(e.ata) // I love you e.ource. PostMessage (e) {console.log(e.ata) // I love you e.ource.'I don't love you', e.origin)
 }
Copy the code

4.websocket

Websocket is a persistent PROTOCOL for HTML5, which enables full-duplex communication between browsers and servers, and is also a cross-domain solution. WebSocket and HTTP are both application-layer protocols based on TCP. However, WebSocket is a two-way communication protocol. After a connection is established, the Server and client of WebSocket can actively send or receive data to each other. At the same time, WebSocket needs to use HTTP protocol when establishing a connection. After the connection is established, the two-way communication between the client and the server has nothing to do with HTTP.

The native WebSocket API is not easy to use, so we use socket. IO, which encapsulates the WebSocket interface nicely, providing a simpler, more flexible interface, and downward compatibility for browsers that don’t support WebSocket.

Let’s start with an example: the local file socket.html generates and receives data to localhost:3000

// socket.html
<script>
    let socket = new WebSocket('ws://localhost:3000');
    socket.onopen = function () {
      socket.send('I love you'); // Send data to the server} socket.onMessage =function(e) { console.log(e.data); // receive the data from the server} </script>Copy the code
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws'); // Remember to install wslet wss = new WebSocket.Server({port:3000});
wss.on('connection'.function(ws) {
  ws.on('message'.function (data) {
    console.log(data);
    ws.send('I don't love you')}); })Copy the code

5. Node middleware Broker (twice cross-domain)

Implementation principle: The same origin policy is a standard that the browser must follow. If the server requests the server, the same origin policy is not required. Proxy server, you need to do the following steps:

  • Client requests are accepted.
  • Forward the request to the server.
  • Get the server response data.
  • The response is forwarded to the client.

Let’s start with an example: a local file index.html file that requests data from the proxy server http://localhost:3000 to the target 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>
Copy the code
// server1.js proxy server (http://localhost:3000) const HTTP = require('http') // Step 1: Const server = http.createserver ((request, response) => {// A proxy server that interacts directly with the browser, Response. WriteHead (200, {'Access-Control-Allow-Origin': The '*'.'Access-Control-Allow-Methods': The '*'.'Access-Control-Allow-Headers': 'Content-Type'Const proxyRequest = http.request ({host:'127.0.0.1',
        port: 4000,
        url: '/', method: request.method, headers: request.headers}, serverResponse => {// step 3: receive the serverResponse var body =' '
        serverResponse.on('data', chunk => {
          body += chunk
        })
        serverResponse.on('end', () => {
          console.log('The data is '+ body) // Step 4: forward the response result to the browser Response.end (body)})}).end()}) server.listen(3000, () => {console.log('The proxyServer is running at http://localhost:3000')})Copy the code
// 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')})Copy the code

It is worth noting that the browser sends the request to the proxy server, which also follows the same origin policy, and finally prints {“title”:”fontend”,”password”:”123456″} in the index.html file.

6. Nginx reverse proxy

Similar to the Node middleware proxy, you need to set up a relay Nginx server to forward requests.

Using the NGINx reverse proxy is the easiest way to cross domains. You can solve cross-domain problems by simply changing the configuration of Nginx, supporting all browsers, supporting sessions, without changing any code, and without compromising server performance.

Implementation idea: NGINx is used to configure a proxy server (with the same domain name as domain1 but different ports) as the jumper. The reverse proxy accesses the interface of Domain2. In addition, the domain information in cookies can be modified to facilitate the writing of cookies in the current domain and realize cross-domain login.

Nginx: nginx: nginx: nginx: nginx

// 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 the domain name in cookie
        index  index.html index.htm;

        # When accessing nignx using middleware proxy interfaces such as Webpack-dev-server, no browser is involved, so there is no same-origin restriction, the following cross-domain configuration is not enabled
        add_header Access-Control-Allow-Origin http://www.domain1.com;  * is used when the current end is cross-domain and does not contain cookies
        add_header Access-Control-Allow-Credentials true; }}Copy the code

Finally, use the nginx -s reload command to launch Nginx

// index.html var xhr = new XMLHttpRequest(); // Front-end switch: whether the browser reads and writes cookies xhr.withCredentials =true; // Access the nginx proxy server xhr.open('get'.'http://www.domain1.com:81/?user=admin'.true);
xhr.send();
Copy the code
// 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)); // Write cookie to front desk res.writehead (200, {'Set-Cookie': 'l=a123456; Path=/; Domain=www.domain2.com; HttpOnly'// HttpOnly: the script cannot be read}); res.write(JSON.stringify(params)); res.end(); }); server.listen('8080');
console.log('Server is running at port 8080... ');
Copy the code

7.window.name + iframe

The unique feature of the window.name property is that the name value will persist across different pages (or even different domain names), and very long name values (2MB) can be supported.

A. HTML and B. HTML are in the same field, both are http://localhost:3000; And c. HTML is http://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// The onLoad event is triggered twice. The first time loads the cross-domain page and stores the data in window.namefunction load() {
      if(first){// After the first onload(cross-domain page) is successful, switch to the co-domain agent pagelet iframe = document.getElementById('iframe');
        iframe.src = 'http://localhost:3000/b.html';
        first = false;
      }else{/ / second onload (sympatric b.h HTML pages) after the success, read the same domain window. The name of the data to the console. The log (iframe. ContentWindow. Name); } } </script>Copy the code

B. HTML is an intermediate proxy page. It is in the same field as A.HTML and the content is empty.

 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = 'I don't love you'  
  </script>
Copy the code

Summary: The SRC attribute of the iframe is transferred from the outer domain to the local domain. The cross-domain data is transferred from the outer domain to the local domain by the window. This neatly circumvents the browser’s cross-domain access restrictions, but at the same time it is a secure operation.

8.location.hash + iframe

Implementation principle: A. HTML wants to communicate with C. HTML across domains, through the middle page B. HTML to achieve. Three pages. Different domains pass values using the location.hash of iframe, and the same domains directly communicate with each other by JS access.

First, A.HTML passes a hash value to C.TML, then C.TML receives the hash value, passes the hash value to B.TML, and finally B.TML puts the result into the hash value of A.HTML. Similarly, a.html and B.TML are in the same field, both are http://localhost:3000; And c. HTML is http://localhost:4000

 // a.html
  <iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
  <script>
    window.onhashchange = function() {// testhashThe change of the console. The log (location. The hash); } </script>Copy the code
/ / b.h HTML < script > window. The parent, the parent. The location. The hash = location. The hash / / b.h HTML a.h will result in HTMLhash</script> </script>Copy the code
 // c.html
 console.log(location.hash);
  let iframe = document.createElement('iframe');
  iframe.src = 'http://localhost:3000/b.html#idontloveyou';
  document.body.appendChild(iframe);
Copy the code

9.document.domain + iframe

This mode can be used only when the same secondary domain name is used. For example, a.test.com and B.test.com are applicable to this mode. You can cross domains simply by adding document.domain =’test.com’ to the page to indicate that the second level domain is the same.

Implementation principle: two pages through JS mandatory set document.domain as the basis of the primary domain, the implementation of the same domain.

Let’s take an example: the page a.zf1.cn:3000/a.html gets the value of a in the page b.zf1.cn:3000/ b.tml

// 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>
Copy the code
// b.html
<body>
   hellob
   <script>
     document.domain = 'zf1.cn'
     var a = 100;
   </script>
</body>
Copy the code

Third, summary

  • CORS supports all types of HTTP requests and is the ultimate solution for cross-domain HTTP requests
  • JSONP only supports GET requests. The advantages of JSONP are support for older browsers and the ability to request data from sites that do not support CORS.
  • Both the Node middleware proxy and the Nginx reverse proxy are unrestricted by the same origin policy.
  • In daily work, the most commonly used cross-domain schemes are CORS and NGINX reverse proxies

I recommend a good BUG monitoring toolFundebug, welcome to the free trial!

Refer to the article

  • Everest Architecture (highly recommended)
  • Description of cross-domain resource sharing CORS
  • The front end of the interview
  • window.postMessage
  • Common cross-domain solutions on the front end (full)
  • Delving into cross-domain problems (4) – Use agents to solve cross-domain problems