A common implementation for cross-domain (non-same-origin policy requests)

Author: Xianke

What is cross-domain

First of all, to understand cross-domain, we need to know what is the same-origin policy

The same origin policy is a security function of the browser. Client scripts from different sources cannot read or write resources from each other without explicit authorization.

Restricts how documents or scripts loaded from the same source can interact with resources from another source. This is an important security mechanism for isolating potentially malicious files

The browser can only access the content of the same domain name. If the protocol, domain name, or port number is different, it will cause cross-domain.

How does cross-domain arise?

For example: pages in one domain request resources in another domain (pages in A domain request resources in B domain)

Let’s look at the error message caused by cross-domain:

How many ways can you solve cross-domain problems?

1. The json across domains

JSONP implementation

Web pages can get JSON data dynamically generated from other sources by exploiting the vulnerability that script tags have no cross-domain restrictions. JSONP requests must be supported by the other party’s server. JSONP is all GET requests, there are no other requests or synchronous requests, and jQuery clears the cache for JSONP requests by default.

//JS JSONP mode
let script = document.createElement("script");
script.type = "text/javascript";
// Pass the name of the callback function to the back end, so that the back end can execute the callback function defined in the front end when it returns
script.src = "http://www.domain2.com:8080/login?callback=handleCallback";
document.head.appendChild(script);
// The callback executes the function
function handleCallback(res) {
  alert(JSON.stringify(res));
}
Copy the code
//jQuery in jSONP form
$.ajax({
  url: "http://www.domain2.com:8080/login".dataType: "jsonp".type: "get".jsonpCallback: "handleCallback".jsonp: "callback".success: function (data) {
    console.log(data); }});Copy the code

Why does JSONP only support GET requests?

JSONP requests a JAVASCRIPT script and treats the result of executing the script as data. So, can you POST a script that is imported through the script tag? Document.createelement (‘ script ‘) generates a script tag and inserts it into the body. There is no room to format the request at all).

So JSONP is implemented by creating a script tag and placing the REQUESTED API address in SRC. This request can only be a GET method, not a POST

2. The cors across domains

What is the cors

Cross-origin Resource Sharing (CORS) allows the browser to send XMLHttpRequest requests to cross-source servers, thus overcoming the limitation that AJAX can only be used in the same source. The key to CORS communication is the server. As long as the server implements the CORS interface, cross-source communication is possible.

A simple request

(1) Common [simple request] in work: the methods are: GET, HEAD,POST

Request header: No custom header content-Type: Text /plain, multipart/form-data, Application/X-www-form-urlencoded

implementation

/ / page
axios.get("http://127.0.0.1:8888/index").then(function (data) {
  console.log(data);
});
Copy the code
// Server side
Access-control-allow-origin enables CORS.
// This property indicates which domain names can access the resource. If wildcard is set, all websites can access the resource.
let express = require("express");
let app = express();

// Configure middleware to set request headers across domains
app.use(function (req, res, next) {
  res.header("Access-Control-Allow-Origin"."http://127.0.0.1:5500");
  next();
});
app.get("/index".function (req, res) {
  res.send("hello");
});
app.listen(8888.function () {
  console.log("Listening on port 8888 succeeded");
});
Copy the code

Results:

Non-simple request

The request method is PUT or DELETE, or the content-Type field is application/ JSON.

Before for a simple request, the browser will send a preview before formal request request, this is sometimes we can see in the console option request, that is to say, before the formal request, the browser will ask service end this address I can visit you, if you can, the browser will send a formal request, otherwise an error.

How do I identify precheck requests

The request method for the “precheck” request is OPTIONS, indicating that the request is for questioning. In the header information, the key field is Origin, indicating which source the request came from.

In addition to the Origin field, the precheck request header contains two special fields.

  • Access-Control-Request-Method

    This field is required to list which HTTP methods are used by the browser for CORS requests, in this example PUT.

  • Access-Control-Request-Headers

    This field is a comma-separated string that specifies the additional Header field to be sent by a browser CORS request, x-custom-header in the example above.

    After receiving the pre-check Request, the server checks the Origin, Access-Control-request-Method, and access-Control-request-headers fields, and confirms that cross-source requests are allowed.

Fetch Cross-domain request

When using FETCH to initiate a cross-domain request
const body = {name:"Good boy"};
 fetch("http://localhost:8000/API", {headers: {'content-type':'application/json'
     }
     method:'POST'.body: JSON.stringify(body)
}).then(response= >
     response.json().then(json= > ({ json, response }))
).then(({ json, response }) = > {
    if(! response.ok) {return Promise.reject(json);
    }
    return json;
}).then(
    response= > response,
    error= > error
  );
Copy the code
Fetch’s mode configuration item

Fetch provides three modes if the server does not support CORS

For cross-domain request, CORS mode is a common implementation of cross-domain request, but fetch’s no-CORS cross-domain request mode is relatively unknown. This mode has an obvious feature:

This mode allows the browser to send the cross-domain request but cannot access the content returned in the response. This is why the response Type is opaque and transparent.

model describe
same-origin This mode is not allowed to cross domains. It must comply with the same origin policy, otherwise the browser will return an error telling you that it cannot cross domains. The corresponding response type is basic.
cors This pattern supports cross-domain requests in the form of CORS as the name implies; Of course, this mode can also be same-domain requests without the need for additional BACKEND CORS support; The corresponding response type is CORS
no-cors This mode is used for cross-domain requests but the server does not have a CORS response header, that is, the server does not support CORS. This is also the special cross-domain request approach of FETCH; The response type is Opaque.

Ajax requests the use of CORS across domains

Access-control-allow-origin enables CORS. Next, go straight to code */

// Initialize the Ajax object
var xhr = new XMLHttpRequest();
// Connect to address, prepare data
xhr.open("Way"."Address"."Asynchronous or not");
// Receive data to complete the event triggered
xhr.onload =function(){}
// Send data
xhr.send();

Copy the code

The main differences between FETCH and Ajax

  1. Ajax works with MVC frameworks, but FETCH works with MVVM frameworks
  2. Fetch is easier to write than Ajax
  3. .fetch reports errors only for network requests,400 and 500 are regarded as successful requests and need to be encapsulated for processing

Cross-domain parameter transfer of cookie

Responsetheader (” access-Control-allow-origin “,”b.test.com”);

CORS requests do not send cookies and HTTP authentication information by default. To send cookies to the server, specify the access-Control-allow-credentials field with the server’s permission.

 document.cookie="pin=test; domain=test.com;";

//js:
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

//jquery
    $.ajax({
       url:_url,
       type:"get".data:"".dataType:"json".xhrFields: {
          withCredentials: true
  // Developers must enable the withCredentials attribute in the AJAX request. Otherwise, the browser won't send a Cookie, even if the server agrees to do so. Or, if the server asks for a Cookie, the browser won't handle it.
       },
       crossDomain: true,})Copy the code

Cross-domain considerations when using CORS

  • Access-control-allow-origin cannot use wildcards (‘ * ‘) if cookies are used (with withCredentials: true), use the specific Origin
  • Internet Explorer 6/ Internet Explorer 7 does not support Access-Control-allow-Origin
  • Although non-simple requests can be supported by setting the response header and response mode, the client should not be allowed to send non-simple requests unless it is absolutely necessary. Non-simple requests cause twice as much stress on the server as simple requests.
  • If you want to send cookies, access-Control-allow-Origin cannot be set to an asterisk and must specify an explicit domain name consistent with the requested web page.
  • Cookies still follow the same origin policy. Only cookies set with the domain name of the server will be uploaded. Cookies of other domain names will not be uploaded
  • The document.cookie in the original web page code also cannot read cookies under the server domain name.

3. The iframe + document. Domain across domains

If the child domain can’t access the parent domain, we can use the document.domain to set the same domain name. But note that the document.domain is limited, we can set the document.domain to its own or higher parent domain. The primary domains must be the same

implementation

// Parent page 127.0.0.1:5500
    <iframe
      src="http://127.0.0.1:5501/index.html"
      id="iframepage"
      width="100%"
      height="100%"
      frameborder="0"
      scrolling="yes"
      onLoad="getData()"
    ></iframe>
    <script>
      /** * Use document.domain to resolve iframe parent module cross domain */
       window.parentDate = {
        name: "hello world!".age: 18};let parentDomain = window.location.hostname;
      document.domain = parentDomain;
      function getData() {
        console.log("Data passed from subdomain to parent domain", top.childData);
      }
    </script>
Copy the code
// subpage 127.0.0.1:5501<h1> Sub-page </h1><script>
      let childDomain = document.domain;
      document.domain = childDomain;
      top.childData = {
        name: "Hello world!.age: 26};let parentDate = top.parentDate;
      console.log("Data obtained from the parent domain", parentDate);
</script>
Copy the code

Note: However, if you want to make a direct ajax request to the example.com/b.html page from the www.example.com/a.html page, even if you set the same document.domain, it still won’t work. So modifying document.domain only works for interactions between frameworks in different subdomains.

4. The iframe + location. The hash across domains

A wants to communicate with B across domains through the middle page C. Three pages, different fields use iframe location.hash to transfer values, the same fields directly js access to communicate.

<! - a.h HTML -- > http://127.0.0.1:5501/a.html 
<iframe id="iframe" src="http://127.0.0.1:5501/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // Pass hash values to B.html
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // callback methods open to homologous C.HTML
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

Copy the code
<! - b.h HTML > http://127.0.0.1:5501/b.html-- - 
<iframe id="iframe" src="http://127.0.0.1:5500/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');
    // listen for hash values from A.html and pass them to C.HTML
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

Copy the code
<! - c. HTML > http://127.0.0.1:5500/c.html-- - 
<script>
  // Listen for hash values from B.html
 window.onhashchange = function () {
  // Return the result by manipulating the javascript callback of the same domain A.html
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user='.' '));
    };
</script>

Copy the code

5. The iframe + window. The name across domains

The window.name attribute is unique in that the name value persists across different pages (and even different domain names) and supports very long name values (2MB).

//a.html
let proxy = (url, callback) = > {
    let state = 0;
    let iframe = document.createElement('iframe');

    // Load the cross-domain page
    iframe.src = url;

    // The onload event fires twice, the first time the cross-domain page is loaded and the data is stored in window.name
    iframe.onload = function() {
        if (state === 1) {
            // After the second onload(syndomain proxy page) succeeds, the data in syndomain window.name is read
            callback(iframe.contentWindow.name);
            destoryFrame();

        } else if (state === 0) {
            // After the first onload succeeds, switch to the same-domain proxy page
            iframe.contentWindow.location = 'http://www.domain1.com/proxy.html';
            state = 1; }};document.body.appendChild(iframe); 
};
   // After the data is retrieved, the iframe is destroyed to free memory; This also ensures security (not accessed by other fields frame JS)
    function destoryFrame() {
        iframe.contentWindow.document.write(' ');
        iframe.contentWindow.close();
        document.body.removeChild(iframe);
    }
// Request cross-domain B page data
proxy('http://127.0.0.1:5501/b.html'.function(data){
    alert(data);
});

Copy the code
<! --b.html-->
<script>
    window.name = 'access b.h HTML';
</script>

Copy the code

6. PostMessage across domains

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

grammar

postMessage(message, targetOrigin, [transfer]);

parameter instructions
message Data to be sent to other Windows
targetOrigin Specifies which Windows can receive message events, which can be the string “*” for unrestricted, or a URI.
transfer Transferable is a string of Transferable objects that are passed along with Message. Ownership of these objects is transferred to the receiver of the message, and the sender no longer retains ownership

implementation

/ / A page
<iframe src="http://127.0.0.1:8888/B.html" id="ifm"></iframe>
<input type="button" value="ok" onclick = "run()">
<script>
let ifm=document.getElementById('ifm');
function run(){
  ifm.contentWindow.postMessage({name:6666},'http://127.0.0.1:8888')}</script>
Copy the code
/ / B page
<div>111111</div>
<script>
window.addEventListener('message'.function(e){
console.log(e.data)
})
</script>
Copy the code

7. Webpack’s devServer.proxy handles cross-domain

const path = require("path");
module.exports = {
  entry: "./index.js".devServer: {
    host: "0.0.0.0".port: 8080.proxy: {
      "/api": {
        target: "https://www.xxx.com".secure: false.// Must be written when the protocol is HTTPS
        changeOrigin: true.//changeOrigin: true to cross-domain}},},//... omitted
};

// After the configuration is complete, restart the service and restart the project as required: NPM run serve or NPM run dev.
Copy the code

Why can reverse proxies cross domains

That is, under normal circumstances, a.com is unable to send requests to B.com. But if you start a server, a.com sends a request to a.com/api, the server receives the request from a.com/api, sends a request to b.com, gets the result returned by B.com and throws it back, then there is no cross-domain. Because there are no cross-domain restrictions on the server side. At this point, the server acts as a layer of proxy. A.com only needs to deal with a.com/api. Reverse proxy is usually for servers. For example, if you visit www.baidu.com, you are actually accessing a proxy server. The proxy server distributes requests to multiple servers, thus providing a layer of isolation. So the reverse proxy is for the server and the forward proxy is for the client.

How does changeOrigin address cross-domain issues

ChangeOrigin only changes the host in the Request

summary

  1. JSONP has the advantage of simple compatibility and can be used to solve the problem of cross-domain data access in mainstream browsers. The disadvantage is that only GET requests are supported, which is not secure.
  2. JSONP is a GET form, which carries limited information, so CORS is the best choice when the information is large.
  3. If the second parameter of the data segment is “*”, it means that the data can be retrieved by any field. In this case, construct window.addeventListener to receive the data.
  4. Nowadays, it is a basic configuration for front-end development to use Webpack-dev-server as the local server, and the proxy function can well cope with SSL, cross-domain, online environment switch and other requirements. Vue CLI 3 also does the corresponding integration, it is very convenient to use.