MEMEPh. ideas that are worth sharing...

Nine cross-domain implementation principles (full version)

Front-end and back-end data interactions often encounter cross-domain requests. What is cross-domain, and what are the several cross-domain methods? This is the content of this article.

 

1. 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 core and most basic security function of the browser. If the same-origin policy is missing, the browser is vulnerable to XSS, CSRF and other attacks. 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 homologous. The same-origin policy restrictions include:

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

 

2. Common cross-domain scenarios

When any of the protocol, subdomain name, main domain name, and port number are different, they are counted as different domains. Requesting resources from each other between different domains is considered "cross-domain". Common cross-domain scenarios are shown in the following figure:

Two points in particular:

First: If it is a cross-domain problem caused by protocols and ports, the "foreground" is powerless.

Second: In the cross-domain problem, it is only identified by the "header of the URL" and not based on whether the IP addresses corresponding to the domain name are the same. "The header of the URL" can be understood as "the protocol, domain name and port must match”.

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

Cross-domain does not mean that the request cannot be sent, 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 wonder if you can initiate cross-domain requests through forms, why not Ajax? Because in the final analysis, cross-domain is to prevent users from reading the content of another domain name, Ajax can get the response, the browser thinks this is not Safe, so the response is intercepted. But the form doesn't get new content, so cross-domain requests can be made. It also shows that cross-domain cannot completely prevent CSRF, because the request is sent after all.

 

2. Cross-domain solutions


1.jsonp

 

1) Principle of JSONP

By exploiting the vulnerability that <script>tags do not have cross-domain restrictions, web pages can obtain JSON data dynamically generated from other sources. JSONP requests must be supported by the other party's server.

 

2) JSONP and AJAX comparison

JSONP is the same as AJAX, both of which are the way that the client sends a request to the server and obtains data from the server. But AJAX belongs to the same-origin policy, JSONP belongs to the non-same-origin 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 by mainstream browsers. The disadvantage is that only supporting the get method has limitations, and it is not safe and may suffer from XSS attacks.

 

4) Implementation process of JSONP

In development, it may be encountered that the callback function names of multiple JSONP requests are the same. In this case, you 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=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: 'Iloveyou' },
  callback: 'show'
}).then(data => {
  console.log(data)
})

 

The above code is equivalent to http://localhost:3000/say?wd=Iloveyou&callback=show requesting data from this address, then returning to the background show('I do not love you'), and finally running the show() function to print '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 do not love you')`)
})
app.listen(3000)

 

 

5) jQuery's jsonp form

JSONP is all GET and asynchronous requests, there are no other request methods and synchronous requests, and jQuery will clear the cache for JSONP requests by default.

$.ajax({
url:"http://crossdomain.com/jsonServerResponse",
dataType:"jsonp",
type:"get",//can be omitted
jsonpCallback:"show",//Customize the function name passed to the server, instead of using jQuery automatically generated, can be omitted
jsonp:"callback",//->Put the parameter that passes the function name callback,can be omitted
success:function (data){
console.log(data);}
});

 

 

2.cors

CORS requires both browser and backend support. IE 8 and 9 need to do this via XDomainRequest .

The browser will automatically carry out CORS communication, and the key to realizing CORS communication is the backend. As long as the backend implements CORS, cross-domain is implemented.

The server can enable CORS by setting Access-Control-Allow-Origin. This attribute indicates which domain names can access the resource. If a wildcard is set, it means that all websites 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 requests, namely simple requests and complex requests.

 

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:

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

None of the XMLHttpRequestUpload objects in the request have registered any event listeners; XMLHttpRequestUpload objects can be accessed using the XMLHttpRequest.upload property.

 

2) complex request

A request that does not meet the above conditions is definitely a complex request.
For CORS requests of complex requests, an HTTP query request will be added before the formal communication, which is called a "preflight" request. This request is an option method, and this request is used to know whether the server allows cross-domain requests.

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

// Which method is allowed to access me

res.setHeader('Access-Control-Allow-Methods', 'PUT')
res.setHeader('Access-Control-Max-Age', 6)

// OPTIONS , The request does nothing
if (req.method === 'OPTIONS') {
res.end()
}

// Define the content returned by the background

app.put('/getData', function(req, res) {
console.log(req.headers)
res.end('I don't love you')
})

 

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 cannot cross domain
xhr.withCredentials = true // Whether the front-end settings have 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) {
            console.log(xhr.response)
            //Get the response header, set Access-Control-Expose-Headers in the background
            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'] //Set the whitelist
app.use(function (req, res, next) {
    let origin = req.headers.origin
    if (whitList.includes(origin)) {
        // set which source can access me
        res.setHeader('Access-Control-Allow-Origin', 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 to carry cookies
        res.setHeader('Access-Control-Allow-Credentials', true)
        // Preflight survival time
        res.setHeader('Access-Control-Max-Age', 6)
        // allow headers to be returned
        res.setHeader('Access-Control-Expose-Headers', 'name')
        if (req.method === 'OPTIONS') {
            res.end() // OPTIONS request does not do anything
        }
    }

} 
    next()

)

app.put('/getData', function (req, res) {
    console.log(req.headers)
    res.setHeader('name', 'jw') //return a response header, which needs to be set in the background
    res.end('I dont love you')

})

app.get('/getData', function (req, res) {
    console.log(req.headers)
    res.end('I dont love you')

})

app.use(express.static(__dirname))
app.listen(4000)

 

The above code is made http://localhost:3000/index.htmlto http://localhost:4000/cross-domain request, as we said above, the backend is the key to realize CORS communication.

 

3.postMessage

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

The postMessage() method allows scripts from different sources to communicate asynchronously with limited communication, enabling cross-text document, multi-window, and cross-domain messaging .

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

Next let's look at an example: http://localhost:3000/a.html the page http://localhost:4000/b.html passes "I love you" to the page, and the latter returns "I don't love you".

// a.html

<iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe> // wait for it to load to trigger 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.data) //I don't love you
} 
} 

</script>

 

// b.html
window.onmessage = function(e) {
console.log(e.data) //I love you
e.source.postMessage('I don't love you', e.origin)
} }

 

 

4.websocket

Websocket is a persistent protocol of HTML5, which realizes full-duplex communication between the browser and the server and is also a cross-domain solution. Both WebSocket and HTTP are application-layer protocols, both based on the TCP protocol. But WebSocket is a two-way communication protocol. After the connection is established, both the WebSocket server and client can actively send or receive data to each other. 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 has nothing to do with HTTP.

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

Let's look at an example first: the local file socket.html sends localhost:3000 data to and receives data

// 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 returned by the server
} 
</script>

 

// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//Remember to install ws
let wss = new WebSocket.Server({port:3000});
wss.on('connection',function(ws) {
ws.on('message', function (data) {
console.log(data);
ws.send('I dont love you')
});
})

 

 

5. Node middleware proxy (twice cross domain)

Implementation principle: The same-origin policy is a standard that browsers need to follow, and if it is a server-to-server request, it does not need to follow the same-origin policy.

A proxy server requires the following steps:

Let's look at an example first: the local file index.html file requests data http://localhost:3000 from 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: 'rendc', 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 server (http://localhost:3000)
const http = require('http')

// Step 1: Accept the client request
const server = http.createServer((request, response) => {

// Proxy server, interacts directly with the browser, and needs to set the CORS header field
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type'
  })

// Step 2: forward the request to the server
  const proxyRequest = http
    .request(
      {
        host: '127.0.0.1',
        port: 4000,
        url: '/',
        method: request.method,
        headers: request.headers
      },

      serverResponse => {

// Step 3: Receive the response from the server
        var body = ''
        serverResponse.on('data', chunk => {
          body += chunk
        })

        serverResponse.on('end', () => {
          console.log('The data is ' + body)
 // Step 4: Forward the response to the browser
          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 goes through two cross-domains. It is worth noting that the browser sends a request to the proxy server, and also follows the same-origin policy, and finally prints it 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 build a transit nginx server to forward requests.

Using nginx reverse proxy to achieve cross-domain is the easiest way to cross-domain. 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 (the domain name is the same as domain1, but the port is different) through nginx as a springboard, and the reverse proxy accesses the domain2 interface, and you can modify the domain information in the cookie by the way, which is convenient for writing the current domain cookie and realizes cross-domain login.

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

 

// proxy server

server {
    listen       80;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080; #reverse proxy
        proxy_cookie_domain www.domain2.com www.domain1.com; #Modify the domain name in the cookie
        index  index.html index.htm;

# When accessing nignx with a middleware proxy interface such as webpack-dev-server, there is no browser participation at this time, so there is no same-origin restriction, and the following cross-domain configuration may not be enabled

add_header Access-Control-Allow-Origin http://www.domain1.com; #When the front end only cross-domain without cookies, it can be *

add_header Access-Control-Allow-Credentials true;
    }
}

 

nginx -s reloadFinally start nginx via command line

// index.html
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();
// 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 the foregroun
    res.writeHead(200, {
        'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly: script cannot be read
    });
    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 persists after loading different pages (even different domain names) and can support very long name values ​​(2MB).

Where a.html and b.html are of the same domain, both 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

    function load() {
      if(first){

        let iframe = document.getElementById('iframe');

        iframe.src = 'http://localhost:3000/b.html';

        first = false;

      }else{

        console.log(iframe.contentWindow.name);

      }

    }

  </script>

 

b.html is an intermediate proxy page, the same domain as a.html, and the content is empty.

 // c.html(http://localhost:4000/c.html)
  <script>
    window.name = 'I do not love you'
  </script>

 

Summary: Through the src attribute of the iframe from the foreign domain to the local domain, the cross-domain data is passed from the foreign 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.location.hash + iframe

Implementation principle: a.html wants to communicate with c.html across domains and realizes it through the intermediate page b.html. For three pages, the location.hash of iframe is used to pass values ​​between different domains, and direct js access is used between the same domains to communicate.

Specific implementation steps: at first a.html passes 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 in a.html in the hash value.
Similarly, a.html and b.html are of the same domain, both 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 () { 
      console.log(location.hash);
    }
  </script>

 

 // b.html
  <script>
    window.parent.parent.location.hash = location.hash
  </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

This method can only be used when the second-level domain names are the same, such as a.test.comand b.test.comare applicable to this method .
You only need to add to the page to document.domain ='test.com'indicate that the second-level domain name is the same to achieve cross-domain.

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

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>

 

 

3. Summary