Preface
Front-end and back-end data interaction often encounters cross-domain requests, what is cross-domain, and what kinds of cross-domain approaches are discussed in this paper.
Stamp the complete source code of this article github blog , you will always feel shallow on paper, I suggest you tap the code manually.
1. What is cross-domain?
1. What are homology policies and their limitations?
Homology policy is a convention. It is the most core and basic security function of browser. Without homology policy, browser is vulnerable to XSS, CSRF and other attacks.The so-called homology refers to the same protocol + domain name + port, even if two different domain names point to the same ip address, they are not homologous.
Homology policy restrictions include:
- Storage content such as Cookie, LocalStorage, IndexedDB
- DOM Node
- After the AJAX request was sent, the result was blocked by the browser
However, there are three tags that allow resources to be loaded across domains:
- <img src=XXX>
- <link href=XXX>
- <script src=XXX>
2. Common cross-domain scenarios
When any of the protocols, subdomains, primary domains, and port numbers are not the same, they are counted as different domains.Requesting resources from one domain to another is considered "cross-domain".Common cross-domain scenarios are as follows:
Two special points are noted:
First, if the cross-domain issues caused by protocols and ports are "foreground" there is nothing you can do.
Second, on cross-domain issues, only the "top of the URL" is used to identify the domain name, not the same IP address."The first part of the URL" can be understood as "protocol, domain name and port must match".
Here you may have a question: if the request is cross-domain, did it actually go out?
Cross-domain is not 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 doesn't make cross-domain requests when it's clear that they can be made via a form? Because, ultimately, cross-domain is meant to prevent users from reading content under another domain name, Ajax can get a response, and browsers think it's not safe, so they block the response.However, the form does not get new content, so cross-domain requests can be made.It also indicates that cross-domain does not block CSRF entirely because the request was sent after all.
2. Cross-domain Solutions
1.jsonp
1) JSONP PrincipleBy exploiting the bug that the <script>tag has no cross-domain restrictions, web pages can get JSON data generated dynamically from other sources.JSONP requests must be supported by the other party's server.
2) JSONP versus AJAXJSONP, like AJAX, is the way clients send requests to and get 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 JSONPJSONP has the advantage of simplicity and good compatibility and can be used to solve cross-domain data access problems in mainstream browsers.The disadvantage is that only support for get methods has limitations, and insecurity can be subjected to XSS attacks.
4) Implementation process of JSONP- Declare a callback function whose function name (such as show) acts as a parameter value to be passed to the server requesting data across domains, and whose parameter is to get the target data (the data returned by the server).
- Create a <script> tag, assign that cross-domain API data interface address to the script's src, and pass the function name to the server at that address (via question mark argument:?callback=show).
- When the server receives the request, it needs special handling: stitching 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 is show('I don't love you').
- Finally, the server returns the prepared data to the client through the HTTP protocol, and the client calls the callback function (show) declared before execution 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(`$=$`) } script.src = `$?$` document.body.appendChild(script) }) } jsonp({ url: 'http://localhost:3000/say', params: { wd: 'Iloveyou' }, callback: 'show' }).then(data => { console.log(data) }) Copy Code
The above code corresponds toHttp://localhost: 3000/say? Wd=Iloveyou&callback=show requests data at this address, then returns to show('I don't love you') in the background, and finally runs show() 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(`$('I don't love you')`) }) app.listen(3000) Copy Code5) jsonp form of jQuery
JSONP is both GET and asynchronous. There are no other requests and synchronous requests, and jQuery clears 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 automatically generated using jQuery, omit jsonp:"callback",//->callback the parameter that passes the function name, omitting success:function (data){ console.log(data);} }); Copy Code
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 achieve CORS communication is the back-end.As long as the back-end implements CORS, it implements cross-domain.
The server can turn on CORS by setting Access-Control-Allow-Origin.This property indicates which domain names can access resources, and if a wildcard character is set, all Web sites can access resources.
Although setting CORS has nothing to do with front-end settings, solving cross-domain problems in this way can result in two scenarios when sending requests, simple and complex.
1) Simple requestA simple request is one that meets both of the following two conditions
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 event listener is registered for any XMLHttpRequestUpload object in the request; the XMLHttpRequestUpload object can be used XMLHttpRequest.upload Property access.
2) Complex RequestsRequests that do not meet the above criteria must be complex.CORS requests for complex requests are preceded by an additional HTTP query request, known as a "preview" request, which is an option method by which the server knows whether cross-domain requests are allowed.
When we request from the background with PUT, 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') // Survival time of pre-examination res.setHeader('Access-Control-Max-Age', 6) // OPTIONS request is not processed if (req.method === 'OPTIONS') { res.end() } // Define what is returned in the backgroundApp.put('/getData', function(req, res) { console.log(req.headers) res.end('I don't love you') }) Copy Code
Next, let's look at the next example of a complete complex request and describe the fields associated with CORS requests
// index.html let xhr = new XMLHttpRequest() document.cookie = 'name=xiamen' // Cookies cannot cross domains xhr.withCredentials = true // Front End Settings with or without cookie s 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 and set Access-Control-Expose-Headers in the backgroundConsole.log(Xhr.getResponseHeader('name')) } } } xhr.send() Copy Code
//server1.js let express = require('express'); let app = express(); app.use(express.static(__dirname)); app.listen(3000); Copy Code
//server2.js let express = require('express') let app = express() let whitList = ['http://localhost:3000'] //Set WhitelistApp.use(function(req, res, next) { let origin = req.headers.origin if (whitList.includes(origin)) { // Set which source can access meRes.setHeader('Access-Control-Allow-Origin', origin) // Which head is allowed to carry to visit meRes.setHeader('Access-Control-Allow-Headers', 'name') // Which method is allowed to access meRes.setHeader('Access-Control-Allow-Methods', 'PUT') // Allow cookies to be carriedRes.setHeader('Access-Control-Allow-Credentials', true) // Survival time of pre-examinationRes.setHeader('Access-Control-Max-Age', 6) // Headers Allowed to ReturnRes.setHeader('Access-Control-Expose-Headers', 'name') if (req.method === 'OPTIONS') { res.end() // OPTIONS request is not processed}} next()}App.put('/getData', function(req, res) { console.log(req.headers) res.setHeader('name', 'jw') //Return a response header with background settingsRes.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) Copy Code
The code above isHttp://localhost: 3000/Index.htmltowardsHttp://localhost: 4000/Cross Domain Request, as we mentioned above, backend is the key to CORS communication.
3.postMessage
PosMessage is an API in HTML5 XMLHttpRequest Level 2 and is one of the few window s attributes that can be manipulated across domains. It can be used to solve the following problems:
- Data transfer for pages and new windows they open
- Messaging between multiple windows
- Page and nested iframe messaging
- Cross-domain data transfer for the three scenarios above
The postMessage() method allows scripts from different sources to communicate in an asynchronous manner with limited limitations, enabling Cross-text file, multi-window, and cross-domain messaging.
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: Data to be sent to other window s.
- TargetOrigin: Specifies which windows receive message events through the window's origin property, which can be a string'*'(meaning unlimited) or a URI.When sending a message, if either 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; only if the three match exactly, the message will be sent.
- Transfer (optional): A sequence of Transferable objects that are passed along with a message. Ownership of these objects will be transferred to the recipient of the message, and the sender will no longer have ownership.
Let's take an example:http://localhost:3000/a.html page toHttp://localhost: 4000/b.html delivers "I love you", then 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 until it finishes loading to trigger an event //Embedded inHttp://localhost: 3000/a.html <script> function load() { let frame = document.getElementById('frame') frame.contentWindow.postMessage('I love you', 'http://localhost:4000') //Send dataWindow.onmessage= function(e) { //Accept returned dataConsole.log(e.data) //I don't love you} </script> Copy Code
// b.html window.onmessage = function(e) { console.log(e.data) //I love you e.source.postMessage('I don't love you', e.origin) } Copy Code
4.websocket
Web ocket is a persistent protocol for HTML5 that enables full duplex Browser-Server communication and is also a cross-domain solution.WebSocket and HTTP are both application layer protocols and are based on TCP protocol.However, WebSocket is a two-way communication protocol. After a connection is established, both servers and clients of WebSocket can actively send or receive data to each other.At the same time, WebSockets need the help of HTTP protocol to establish a 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, we use it Socket.io It encapsulates the webSocket interface well, provides a simpler and more flexible interface, and provides downward compatibility for browsers that do not support webSockets.
Let's start with an example: local filesSocket.htmltowardsLocalhost:3000Occurring and accepting 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 data returned by server} </script> Copy Code
// server.js let express = require('express'); let app = express(); let WebSocket = require('ws');//Remember to install ws let wss = new WebSocket.Server(); wss.on('connection',function(ws) { ws.on('message', function (data) { console.log(data); ws.send('I don't love you') }); }) Copy Code
5. Node middleware proxy (two cross-domains)
Implementation principle: Homology policy is the standard browsers need to follow, but it is not necessary if the server requests from the server.Proxy server, the following steps are required:
- Accept client requests.
- Forward the request to the server.
- Get the server response data.
- Forward the response to the client.
Let's start with an example: local filesIndex.htmlFiles, through a proxy serverHttp://localhost: 3000 to target serverHttp://localhost: 4000 requests 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> Copy Code
// server1.js proxy server (http://localhost:3000) const http = require('http') // Step 1: Accept client requests const server = http.createServer((request, response) => { // Proxy server, which interacts directly with the browser, requires setting the first 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 server const proxyRequest = http.request (, 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 results to the browserResponse.end(body)}} end ()}Server.listen(3000, () => ) Copy 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 Code
The above code goes through two cross-domains, it is worth noting that the browser sends requests to the proxy server, also follows the homology policy, and finally inIndex.htmlThe file prints out {"title":"fontend","password":"123456"}
6.nginx reverse proxy
The implementation works like a Node middleware proxy, requiring you to set up a staging nginx server to forward requests.
Using the nginx reverse proxy to achieve cross-domain is the simplest cross-domain approach.Simply modifying nginx's configuration solves cross-domain problems, supports all browsers, supports session s, does not require any code modifications, and does not affect server performance.
The main idea is to configure a proxy server (domain name is the same as domain1, port is different) as a springboard through nginx. The reverse proxy accesses the domain2 interface, and it can conveniently modify the domains information in the cookie to facilitate the writing of the current domain cookies and achieve cross-domain login.
Download first nginx And then add theNginx.confModify 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 nignx is accessed using a middleware proxy interface such as webpack-dev-server, no browser is involved, so there is no homology restriction. The following cross-domain configuration may not be enabled add_header Access-Control-Allow-Origin http://www.domain1.com; #When the current end only crosses domains without cookie s, it can be * add_header Access-Control-Allow-Credentials true; } } Copy Code
Finally, start nginx from the command line nginx-s reload
// index.html var xhr = new XMLHttpRequest(); // Front-end switch: whether the browser reads and writes cookie s xhr.withCredentials = true; // Accessing the proxy server in nginx xhr.open('get', 'http://www.domain1.com:81/?user=admin', true); xhr.send(); Copy 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 s to the foreground res.writeHead(200, { 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly: The script cannot read};Res.write(JSON.stringify(params));Res.end();};Server.listen('8080'); console.log('Server is running at port 8080...'); Copy Code
7.window.name + iframe
Window.nameThe uniqueness of the property is that the name value persists after loading on different pages (or even different domain names) and can support very long name values (2MB).
Where a.html and b.html are of the same domain, they are bothHttp://localhost: 3000; while 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 // The onload event triggers twice, the first time a cross-domain page is loaded, and the data is persisted inWindow.name function load() { if(first){ // Switch to the same domain proxy page after the first successful onload let iframe = document.getElementById('iframe'); iframe.src = 'http://localhost:3000/b.html'; first = false; }else{ // Read the same domain after the second onload (same domain b.html page) succeedsWindow.nameMedium dataConsole.log(Iframe.contentWindow.name);}} </script> Copy Code
b.html is an intermediate proxy page with the same domain as a.html and empty content.
// c.html(http://localhost:4000/c.html) <script> window.name = 'I don't love you' </script> Copy Code
Summary: Cross-domain data is converted from iframe's src attribute to local domainWindow.namePass from Outer Domain to Local Domain.This skillfully bypasses the browser's cross-domain access restrictions, but it is also a secure operation.
8.location.hash + iframe
Implementation principle: a.html to communicate with c.html across domains, through the middle page b.html.Three pages, using iframe'sLocation.hashPass value, direct js access to communicate between the same domains.
The implementation steps are as follows: 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 into the hash value of a.html.Similarly, a.html and b.html are domains, bothHttp://localhost: 3000; while c.html isHttp://localhost: 4000
// a.html <iframe src="http://localhost:4000/c.html#iloveyou"></iframe> <script> window.onhashchange = function () { //Testinghash Changes console.log(location.hash); } </script> Copy Code
// b.html <script> window.parent.parent.location.hash = location.hash //b.html puts the result in a.html'shash In the value, b.html Through parent.parent Visit a.html page </script> Copy 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 Code
9.document.domain + iframe
This method can only be used when the secondary domain name is the same, such as a.Test.comAnd B.Test.comThis applies.Just add to the pageDocument.domain='Test.com'Indicates that cross-domain can be achieved if both secondary domain names are identical.
How to do this: Both pages are forced through JSDocument.domainOn the basis of the primary domain, the same domain is implemented.
Let's take an example: Page a.zf1.cn:3000/a.html Gets the value of a in page b.zf1.cn:3000/b.html
// 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 Code
// b.html <body> hellob <script> document.domain = 'zf1.cn' var a = 100; </script> </body> Copy Code
3. 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 advantage of supporting older browsers and being able to request data from sites that do not support CORS.
- Whether it is the Node middleware proxy or the nginx reverse proxy, it is mainly through the homology policy that the server is not restricted.
- The most frequently used cross-domain scenarios in daily work are cors and nginx reverse proxies
Author: Boat sailing in the waves
Links:https://juejin.im/post/5c23993de51d457b8c1f4ee1
Source: Excavation