Implementation Principles of Nine Cross-Domain Methods (Full Version)

  Front end, html, javascript, node.js, Programmer

Preface

The front-end and back-end data interactions often encounter cross-domain requests, what is cross-domain and which kinds of cross-domain methods are discussed in this paper.

This article complete source code please fierce stampGithub blog, the paper finally feel shallow, suggest to knock on the code

First, what is cross-domain?

1. What are cognate policies and their restrictions?

Homologous policy is a kind of agreement. It is the most core and basic security function of browser. Without homologous policy, browser is vulnerable to XSS, CSRF and other attacks. Homologous 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.
url的组成
Homologous policy restrictions include:

  • Cookie, LocalStorage, IndexedDB and other storage content
  • DOM node
  • After the AJAX request was sent, the result was intercepted by the browser.

However, 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, primary domain name and port number is different, it is counted as a different domain.. Requests for resources between different domains are considered “cross-domain.” Common cross-domain scenarios are shown in the following figure:

In particular, two points should be noted:

First, there is nothing the “front desk” can do if it is a cross-domain problem caused by protocols and ports.

Second, on the cross-domain issue, only the “URL header” is used to identify it, not whether the IP addresses corresponding to domain names are the same. “URL header” can be understood as “protocol, domain name and port must match”.

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

Cross-domain does not mean that the request cannot be sent out, the request can be sent out, the server can receive the request and return the result normally, but the result is intercepted by the browser. You may wonder why Ajax is not possible when cross-domain requests can be initiated through forms. Because in the final analysis, cross-domain is to prevent users from reading the content under another domain name. Ajax can obtain the response. The browser thinks this is unsafe, so it intercepts the response. However, the form does not get new content, so cross-domain requests can be initiated. At the same time, it also shows that cross-domain cannot completely block CSRF, because the request is sent after all.

Second, cross-domain solutions

1.jsonp

1) JSONP principle

utilize<script>The tag has no cross-domain limitation vulnerability, and the web page can obtain JSON data dynamically generated from other sources. JSONP requests must be supported by the other party’s server.

2) Comparison between JSONP and AJAX

JSONP and AJAX are the same, both of which are the ways that clients send requests to the server and obtain data from the server. However, AJAX is a homologous policy and JSONP is a non-homologous policy (cross-domain request)

3) advantages and disadvantages of jsonp

JSONP has that advantage of simplicity and good compatibility, and can be used to solve the problem of cross-domain data access of mainstream browser.The disadvantage is that only the get method is limited and may be attacked by XSS if it is unsafe.

4) JSONP implementation process

  • Declare a callback function whose function name (such as show) is used as the parameter value to be passed to the server that requests data across domains. The function parameter is the target data (data returned by the server).
  • Create a<script>Tag, assign the cross-domain API data interface address to src of script, and pass the function name to the server in this address (you can pass the parameter through question mark:? callback=show)。
  • After receiving the request, the server needs to perform special processing: splice the function name passed in and the data it needs to give you into a string, for example: the function name 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, and the client 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, so 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 tohttp://localhost:3000/say? wd=Iloveyou&callback=showThis address requests data and then returns in the background.Show ('I don't love you')Finally, I will run the show () function and 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 don't love you') `)
 })
 app.listen(3000)

5) jsonp form of jquery

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

$.ajax({
 url:"http://crossdomain.com/jsonServerResponse",
 dataType:"jsonp",
 Type:"get",// can be omitted
 JsonpCallback:"show",//- > customize the name of the function passed to the server instead of using jQuery to generate it automatically, which can be omitted.
 Jsonp:"callback",//- > can omit the formal parameter callback of the transfer function name.
 success:function (data){
 console.log(data);  }
 });

2.cors

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

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

The server can open CORS by setting Access-Control-Allow-Origin. This attribute indicates which domain names can access resources, and if wildcards are set, all websites can access resources.

Although setting CORS has nothing to do with the front end, there are two situations when sending requests to solve cross-domain problems in this way, namelySimple requestAndComplex 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 2:Content-Type is limited to one of the following three:

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

Any XMLHttpRequestUpload object in the request has no event listener registered; The XMLHttpRequest.upload object can be accessed using the XMLHttpRequest.upload attribute.

2) Complex requests

Requests that do not meet the above conditions are definitely complex requests.
CORS requests for complex requests will be added with an HTTP query request, called “preflight” request, before formal communication. The request is option-based, and it is used to know whether the server allows cross-domain requests.

We usePUTWhen requesting from the background, it is a complex request. The background needs to be configured as follows:

//Which method is allowed to access me
 res.setHeader('Access-Control-Allow-Methods', 'PUT')
 //Survival Time of Preflight
 res.setHeader('Access-Control-Max-Age', 6)
 // OPTIONS request is not processed
 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 the next example of a complete complex request and introduce the fields related to the CORS request.

//  index.html
 let xhr = new XMLHttpRequest()
 Cookie =' name = xiamen'//cookie cannot cross domains.
 With credentials = true//front end setting whether cookie is present.
 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)
 //to get the response header, the background needs to set 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']//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 head are allowed to visit me
 res.setHeader('Access-Control-Allow-Headers', 'name')
 //Which method is allowed to access me
 res.setHeader('Access-Control-Allow-Methods', 'PUT')
 //cookie allowed
 res.setHeader('Access-Control-Allow-Credentials', true)
 //Survival Time of Preflight
 res.setHeader('Access-Control-Max-Age', 6)
 //headers allowed to return
 res.setHeader('Access-Control-Expose-Headers', 'name')
 if (req.method === 'OPTIONS') {
 End ()//options request is not processed
 }
 }
 next()
 })
 app.put('/getData', function(req, res) {
 console.log(req.headers)
 Res.setHeader('name', 'jw') // returns a response header, which needs to be set in the background
 Res.end ('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)

The above code is composed ofhttp://localhost:3000/index.htmlTohttp://localhost:4000/Cross-domain requests, as we said above, the backend is the key to CORS communication.

3.postMessage

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

  • Data Transfer of Pages and New Windows Opened by Pages
  • Message delivery between multiple windows
  • Pages and nested iframe messaging
  • Cross-domain data transfer in the above three scenarios

The postMessage () method allows scripts from different sources to communicate in an asynchronous manner with limited communication, and can realize cross-text file, multi-window and cross-domain messaging..

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

  • Message: data to be sent to other window.
  • Targetorigin: Specify which windows can receive message events through the window’s Origin attribute. Its value can be the string “* ” (meaning unlimited) or a URI. When sending a message, if any one of 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 transferred objects that are passed at the same time as message. Ownership of these objects will be transferred to the receiver of the message, while the sender will no longer retain ownership.

Next let’s look at an example:http://localhost:3000/a.htmlPage orientationhttp://localhost:4000/b.htmlPass “I love you” and then the latter returns “I don’t love you”.

//  a.html
 < iframe src = "http://localhost: 4000/b.html" frameorder = "0" id = "frame" onload = "load ()" > </iframe >//until it is loaded and triggers an event
 //embedded at 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 returned data
 I don't love you.
 }
 }
 </script>
//  b.html
 window.onmessage = function(e) {
 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 browser and server, and is also a cross-domain solution. Both WebSocket and HTTP are application layer protocols and are based on TCP protocol. butWebSocket is a two-way communication protocol. After the 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 the connection. After the connection is established, the two-way communication between client and server has nothing to do with HTTP.

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

Let’s look at an example first: the local file socket.htmllocalhost:3000Occurrence data and acceptance data

//  socket.html
 <script>
 let socket = new WebSocket('ws://localhost:3000');
 socket.onopen = function () {
 Socket.send ('I love you');  //Send data to 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 don't love you')
 });
 })

5. Node Middleware Agent (Cross Domain Twice)

Implementation principle:Homologous policy is the standard that browsers need to follow, but if the server requests from the server, it does not need to follow the homologous policy.
Proxy server, 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 first: the local file index.html file, through the proxy serverhttp://localhost:3000To target serverhttp://localhost:4000Request data.

// 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 server (http://localhost:3000)
 const http = require('http')
 //Step 1: Accept the client request
 const server = http.createServer((request, response) => {
 //Proxy server, which directly interacts with browser, needs to set the header field of CORS
 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 a 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 result 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 the request to the proxy server, and also follows the homologous policy. Finally, the file is printed out in index.html{"title":"fontend","password":"123456"}

6.nginx reverse proxy

The implementation principle is similar to Node middleware proxy. You need to build a transit nginx server for forwarding requests.

Using nginx reverse proxy to realize cross-domain is the simplest cross-domain method. Only the configuration of nginx needs to be modified to solve the cross-domain problem. It supports all browsers, supports session, does not need to modify any code, and does not affect server performance.

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

Download firstnginx, and then modify the nginx. conf in the NGINX directory as follows:

// proxy server
 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 ;  # Modify domain name in cookie
 index   index.html  index.htm ;
 
 # when accessing nignx with middleware proxy interfaces such as webpack-dev-server, there is no browser participation at this time, so there is no homology restriction, and the following cross-domain configuration may not be enabled
 add_header Access-Control-Allow-Origin  http://www.domain1.com ;  # Can be *
 add_header Access-Control-Allow-Credentials true;
 }
 }

Finally, through the command linenginx -s reloadStart nginx

//  index.html
 var xhr = new XMLHttpRequest();
 //Front-end switch: whether the browser reads or writes cookie
 xhr.withCredentials = true;
 //Access 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 Front Desk
 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

What makes the window.name attribute unique is that the name value still exists after different pages (even different domain names) are loaded and can support very long name values (2MB).

Among them, a.html and b.html are both in the same regionhttp://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 event will trigger 2 times, loading cross-domain page for the first time and saving data in window.name
 function load() {
 if(first){
 //After the first onload (cross-domain page) succeeds, switch to the same domain proxy page
 let iframe = document.getElementById('iframe');
 iframe.src = 'http://localhost:3000/b.html';
 first = false;
 }else{
 //After the 2nd onload (same domain b.html page) succeeds, read the data in the same domain window.name
 console.log(iframe.contentWindow.name);
 }
 }
 </script>

B.html is the 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 don't love you'
 </script>

Summary: Cross-domain data is transferred from foreign domain to local domain through the src attribute of 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

Principle of Implementation: a.html wants to communicate with c.html across domains through the middle page of b.html. Three pages, the location.hash of iframe is used to transmit values between different domains, and js access is directly used to communicate between the same domains.

The specific implementation steps are as follows: at first, a.html transmits a hash value to c.html, then c.html transmits the hash value to b.html after receiving the hash value, and finally b.html puts the result into a.html’s hash value.
Similarly, a.html and b.html are of the same region, both of themhttp://localhost:3000; And c.html ishttp://localhost:4000

//  a.html
 <iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
 <script>
 Onhashchange = function () {//detect hash changes.
 console.log(location.hash);
 }
 </script>
//  b.html
 <script>
 window.parent.parent.location.hash = location.hash
 //b.html puts the result into the hash value of a.html, and b.html can access the a.html page through 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

This method can only be used when the secondary domain names are the same, for examplea.test.comAndb.test.comApplicable to this method.
Just add to the pagedocument.domain ='test.com'Indicates that cross-domain can be realized if the secondary domain names are the same.

Implementation Principle: Both pages are forced to set document.domain as the basic primary domain through js, thus realizing the same domain.

Let’s look at an example: pagea.zf1.cn:3000/a.htmlGet pageb.zf1.cn:3000/b.htmlThe value of a in

//  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>

III. Summary

  • CORS supports all types of HTTP requests and is the fundamental solution for cross-domain HTTP requests.
  • JSONP only supports GET requests. JSONP has the advantages of supporting old browsers and requesting data from websites that do not support CORS.
  • Whether it is Node middleware agent or nginx reverse agent, the server is not restricted mainly through homologous policies.
  • In daily work, cors and nginx reverse agents are widely used cross-domain schemes

To recommend a useful BUG monitoring toolFundebug, welcome to try free!

Reference article