• What is the same origin policy?
    • Common cross-domain scenarios
    • The same origin policy restricts the following behaviors
  • Cross-domain solutions
    • throughjsonpCross domain
    • CORS
    • document.domain + iframeCross domain
    • location.hash + iframe
    • window.name + iframeCross domain
    • postMessageCross domain
    • nginxAgent cross-domain
    • nodejsMiddleware proxies cross domains
    • WebSocketAgreement cross-domain

What is the same origin policy?

The cornerstone of browser security is the same-Origin Policy.

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

It is the core and most basic security function of the browser. Without the same origin policy, the browser is vulnerable to XSS, CSFR and other attacks

XSS: XSS attack usually refers to the use of the vulnerability left during the development of the web page, through a clever way to inject malicious command code into the web page, the user load and execute the attacker malicious web program cross-site request attack: To put it simply, the attacker uses some technical means to trick the user’s browser into visiting a previously authenticated website and performing some operations (such as sending emails, sending messages, or even property operations such as transferring money or buying goods). Because the browser has been authenticated, the site being visited will act as if it is a genuine user action. This exploits a flaw in user authentication on the Web: simple authentication can only guarantee that a request is sent from a user’s browser, but not that the request itself is voluntarily made by the user

Common cross-domain scenarios

URL Outcome Reason
http://store.company.com/dir/inner/another.html Same origin Only the path differs
http://store.company.com/dir/inner/another.html Same origin Only the path differs
https://store.company.com/page.html Failure Different protocol
http://store.company.com:81/dir/page.html Failure Different port (http:// is port 80 by default)
http://news.company.com/dir/page.html Failure Different host

The same origin policy restricts the following behaviors

  1. Cookie,LocalStorageIndexDBUnable to read
  2. DOMUnable to get
  3. AJAXRequest cannot be sent

Cross-domain solutions

throughjsonpCross domain

A scheme that makes use of script tags without cross-domain constraints

Usually, in order to reduce the load of the Web server, we separate static resources such as JS, CSS and IMG to another server with an independent domain name, and then load static resources from different domain names in the HTML page through corresponding tags, which are allowed by the browser. Based on this principle, we can dynamically create script. Request a reference url to achieve cross-domain communication. Jsonp takes advantage of this feature

The advantages and disadvantages:

  1. JSONPIs a common way for a server to communicate with a client across sources. The biggest feature is simple to apply, the old browser all support, server transformation is very small
  2. Can only achievegetA request that is insecure and vulnerableXSSattack

Example:

function jsonp({ url, params, cb }) {
  return new Promise((resolve, reject) = > {
    let script = document.createElement("script");

    window[cb] = function(data) { resolve(data); }; params = { ... params, cb };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: "haoxl" },
  cb: "show"
}).then(data= > {
  console.log(data);
});
Copy the code

Create an Express server:

app.get("/say".function(req, res) {
  let { wd, cb } = req.query;
  res.end(`${cb}('hello')`);
});

app.listen(3000.function() {
  console.log("Example app listening on port 3000!");
});
Copy the code

CORS

CORS, or Cross-Origin Resource Sharing, is a W3C standard that allows browsers to issue XMLHttpRequest requests to cross-source servers, overcoming the limitation that AJAX can only be used in the same source

Tips: Common cross-domain request: only the server set access-Control-allow-origin, front-end does not need to set, if the request with cookie: both front and back ends need to set. Because of the same-origin policy restriction, the cookie read is the cookie of the domain where the cross-domain request interface resides, not the current page

The advantages and disadvantages:

  1. Currently, all browsers support this feature (IE8+ : IE8/9 is requiredXDomainRequestObject to supportCORS)),CORSIt has also become a mainstream cross-domain solution
  2. The wholeCORSThe communication process is automatically completed by the browser without user participation. For developers,CORSThe communication is no different than homologous AJAX communication, and the code is exactly the same. Once the browser finds outAJAXWhen requests cross sources, additional headers are automatically added, and sometimes an additional request is made, but the user does not feel it
  3. CORSwithJSONPThe use of the same purpose, but more thanJSONPMore powerful.JSONPOnly supportGETCORS supports all types of requestsHTTPThe request.JSONPThe advantage of CORS is that it supports older browsers and can request data from sites that do not support CORS

Two types of requests: Browsers classify CORS requests into two categories:

  1. Simple Request
  2. Non-simple Request (not-so-simple Request)

As long as the following two conditions are met, it is a simple request. Any request that does not meet both of the following conditions is a non-simple request

  1. The request method is one of three methods: GET, HEAD, and POST
  2. The HTTP header does not exceed the following fields: Accept, accept-language, Content-language, and Content-Type. text/plain, multipart/form-data, application/x-www-form-urlencoded), DPR, Downlink, Save-Data, Viewport-Width, Width

Simple request: The browser makes a CORS request directly. Specifically, add an Origin field to the header information.

Non-simple requests: requests that have special requirements on the server, such as the request method PUT or DELETE, or the content-Type field Type application/json. CORS requests that are not simple requests add an HTTP query request, called a preflight request, before formal communication. The browser asks the server if the domain name of the current web page is on the server’s license list, and what HTTP verb and header fields can be used. The browser issues a formal XMLHttpRequest request only if it receives a positive response; otherwise, an error is reported

Request Headers:

  1. Access-Control-Request-Method
  2. Access-Control-Request-Headers

Access-control-request-method This field is required to list the HTTP methods used by the browser for CORS requests

Access-control-request-headers This field is a comma-separated string that specifies the additional header field that the browser will send for CORS requests

Response Headers:

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Copy the code
  1. Access-Control-Allow-Origin
  2. Access-Control-Allow-Credentials
  3. Access-Control-Expose-Headers
  4. Access-Control-Allow-Methods
  5. Access-Control-Allow-Headers
  6. Access-Control-Max-Age

1. Access-control-allow-origin 2. Access-control-allow-credentials This field is optional. Its value is a Boolean that indicates whether cookies are allowed to be sent. By default, cookies are not included in CORS requests. If set to true, the server explicitly approves that cookies can be included in the request and sent to the server. This value can only be set to true if the server does not want the browser to send cookies

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

Access-Control-Allow-Credentials: true
Copy the code

The developer must turn on the withCredentials attribute in the AJAX request

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
Copy the code

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.

However, if the withCredentials setting is omitted, some browsers still send cookies together. In this case, you can explicitly disable the withCredentials

xhr.withCredentials = false;
Copy the code

Note that access-Control-allow-Origin cannot be set to an asterisk if cookies are to be sent, and must specify an explicit domain name consistent with the requested web page. At the same time, cookies still follow the same origin policy, only the Cookie set with the server domain name will be uploaded, cookies of other domain names will not be uploaded, and (cross-source) document. Cookie in the original web page code can not read cookies under the server domain name

3. The access-Control-expose-headers field is optional. In CORS requests, the getResponseHeader() method of the XMLHttpRequest object takes only six basic fields: Cache-control, Content-language, Content-Type, Expires, Last-Modified, Pragma. If you want to get other fields, you must specify access-Control-expose-headers. The above example specifies that getResponseHeader(‘FooBar’) can return the value of the FooBar field

6. Access-control-allow-methods This field is required, and its value is a comma-separated string indicating all Methods supported by the server for cross-domain requests. Notice that all supported methods are returned, not just the one requested by the browser. This is to avoid multiple “pre-check” requests

Access-control-allow-headers the access-Control-allow-headers field is required if the browser Request includes the access-Control-request-headers field. It is also a comma-separated string indicating all header information fields supported by the server, not limited to those requested by the browser in precheck

8. Access-control-max-age Specifies the validity period of the precheck request in seconds. This field is optional. In the result above, the validity period is 20 days (1728000 seconds), which allows the response to be cached for 1728000 seconds (20 days), during which time another precheck request is not issued

Examples of code for localhost:8004

let xhr = new XMLHttpRequest();

// Enforces a request header cookie in the front-end setting
document.cookie = "name=haoxl";

xhr.withCredentials = true;
xhr.open("GET"."http://localhost:3000/getData".true);

// Set the custom request header
xhr.setRequestHeader("name"."haoxl");
xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.warn("xhr.response");
      console.log(xhr.response);
      // get the request header from the background that has changed the value of name
      console.warn("xhr.getResponseHeader");
      console.log(xhr.getResponseHeader("name"));
      console.log("\n"); }}}; xhr.send();Copy the code

Node server code (port: 3000) Example:

var express = require("express");
var app = express();

let whiteList = ["http://localhost:8004"];

app.use(function(req, res, next) {
  let origin = req.headers.origin;

  if (whiteList.includes(origin)) {
    // Set that source to be accessible to me, but not to be used in conjunction with the cookie credential response header
    res.setHeader("Access-Control-Allow-Origin", origin);

    // Allow headers with name to be accessed
    res.setHeader("Access-Control-Allow-Headers"."name");

    // Sets which request methods are accessible
    res.setHeader("Access-Control-Allow-Methods"."GET");

    // Set access to requests with cookies
    res.setHeader("Access-Control-Allow-Credentials".true);

    // If the name header is changed in the background, the browser will consider it unsafe
    res.setHeader("Access-Control-Expose-Headers"."name");

    // Precheck survivability time -options please refer to
    res.setHeader("Access-Control-Max-Age".3);

    // Set no processing when a prerequest is sent
    if (req.method === "OPTIONS") {
      res.end(); //OPTIONS Do not do any processing
    }
  }
  next();
});

app.put("/getData".function(req, res) {
  res.end("hello world");
});

app.get("/getData".function(req, res) {
  res.end("Nice to meet you");
});

app.listen(3000.function() {
  console.log("Example app listening on port 3000!");
});
Copy the code

document.domain + iframeCross domain

This solution applies only to cross-domain scenarios where the primary domain is the same and the subdomains are different (the level-1 domain name of web pages is the same, but the level-2 domain name is different). How it works: The co-domain is realized when both pages are forced to use JavaScript to set document.domain as the base primary domain

Default a.html = www.haoxl.com, b.html = test.haoxl.com

a.html:

<iframe src="http://test.haoxl.com" onload="load()"></iframe>
<script>
  function load() {
    // Tell the page its main domain name to be the same as the main domain name of B.html so that the value of B can be accessed in A
    document.domain = "haoxl.com";
    // After page A introduces page B, directly obtain the value of b in the following way
    console.log(frame.contentWindow.b);
  }
</script>
Copy the code

b.html:

<html>
  <head>
    <title>b.html</title>
    <meta charset="utf8" />
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0" />

    <script>
      window.domain = "haoxl.com";
      function myFn() {
        console.log("onload b.html");
        this.b = "bHtml";
      }
    </script>
  </head>

  <body onload="myFn()">
    <p>b.html</p>
  </body>
</html>
Copy the code

location.hash + iframe

Implementation principle: A and B communicate with each other across domains through the middle page C (and C and A are co-domains). Three pages, different fields use iframe location.hash to pass values, the same fields directly access JavaScript to communicate

A domain: A.html -> B domain: B.html -> A domain: C.HTML, A and B different domain can only hash value one-way communication, B and C are also different domain can only one-way communication, but C and A are the same domain, so C can access all objects on A page through parent. Parent

a.html( www.a.com/a.html ):

<iframe
  id="iframe"
  src="http://www.b.com/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.html(www.b.com/b.html):

<iframe
  id="iframe"
  src="http://www.a.com/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(www.a.com/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

window.name + iframeCross domain

The browser window has the window.name attribute. The most important feature of this property is that, regardless of whether the same source, as long as the previous page in the same window set this property, the next page can read it. And can support very long name values (2MB)

The parent window opens a child window that loads a web page from a different source, which writes information to the window.name property

window.name = data;
Copy the code

Next, the child window jumps back to a url in the same domain as the main window:

location = "http://parent.url.com/xxx.html";
Copy the code

The main window can then read the child window’s window.name:

var data = document.getElementById("myFrame").contentWindow.name;
Copy the code

The advantage of this approach is that window.name is very large and can hold very long strings; The disadvantage is that you must listen for changes in the window.name property of the child window, which affects web page performance

a.html(www.a.com/a.html):

var proxy = function(url, callback) {
  var state = 0;
  var 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 succeeds, the data in the domain 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.a.com/c.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://www.b.com/b.html".function(data) {
  alert(data);
});
Copy the code

C. HTML (www.a.com/c.html): intermediate proxy page, the same domain as A.HTML, the content is empty

b.html(www.b.com/b.html):

<script>
  window.name = "This is b.html data!";
</script>
Copy the code

postMessageCross domain

HTML5 has introduced a new API to solve this problem: Cross-document Messaging API.

The API adds a window.postMessage method to window objects, allowing cross-window communication regardless of whether the two Windows are identical

The first parameter of the postMessage method is the specific content of the message, and the second parameter is the origin of the window that receives the message.

It can be used to solve the following problems:

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

For example, a.js b.js uses node to enable two interfaces with different port numbers to simulate a local implementation across domains

The parent window localhost:4001/a.html sends a message to the child window localhost:4002/b.html by calling the postMessage method. Both parent and child Windows can listen for messages from each other through message events

Usage: The postMessage(data, Origin) method takes two arguments:

  • data: HTML5The specification supports any primitive type or copiable object, but some browsers only support strings, so it’s best to use them when passing argumentsJSON.stringify()serialization
  • origin: Protocol + host + port number. The value can also be set to “*”, indicating that it can be passed to any window. If you want to specify the same source as the current window, set it to “/”.

a.html(http://localhost:3000/a.html):

<html>
  <head>
    <title>1111111111</title>
    <meta charset="utf-8" />
  </head>

  <body>
    <p>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</p>
  </body>

  <script>
    window.onmessage = function(e) {
      console.warn("a.html:");
      console.log(e.data);
      console.warn("e:");
      console.log(e);

      e.source.postMessage("Nice to meet you!", e.origin);
    };
  </script>
</html>
Copy the code

b.html(http://localhost:4000/b.html):

   <html>
  <head>
    <title>bbbbbbbbbbbbbbbbbb</title>
    <meta charset="utf-8" />
  </head>

  <body>
    <iframe
      src="http://localhost:3000/a.html"
      id="frame"
      onload="load()"
    ></iframe>
  </body>

  <script>
    function load() {
      let frame = document.getElementById("frame");

      frame.contentWindow.postMessage(
        "Hello from b.html"."http://localhost:3000"
      );

      window.onmessage = function(e) {
        console.warn("b.html: ");
        console.log(e.data);
      };
    }
  </script>
</html>
Copy the code

nginxAgent cross-domain

Nginx (Engine X) is a high-performance HTTP and reverse proxy server, as well as an IMAP/POP3/SMTP server

Nginx server configuration:

nginx.conf:

server { listen 80; server_name localhost; #charset koi8-r; #access_log logs/host.access.log main; location / { root C:\Projects\Myself\practice; index index.html index.htm; } location /api/ { proxy_pass http://www.jalever.top:4000; Proxy_cookie_domain www.jalever.top localhost; add_header Access-Control-Allow-Origin http://www.jalever.top; * add_header access-control-allow-credentials true; * add_header access-control-allow-credentials true; } # default ... . }Copy the code

Page code:

function load() {
  let xhr = new XMLHttpRequest();

  xhr.withCredentials = true;

  xhr.open("get"."http://localhost:80/api/user=admin".true);
  xhr.send();
}
Copy the code

Nodejs backend code:

var http = require("http");
var server = http.createServer();
var qs = require("querystring");
var PORT = process.env.PORT || 4000;

server.on("request".function(req, res) {
  var params = qs.parse(req.url.substring(2));

  res.writeHead(200, {
    "Set-Cookie": "l=a123456; Path=/; Domain=www.jalever.top; HttpOnly" // HttpOnly: the script cannot read
  });

  res.write(JSON.stringify(params));
  res.end();
});

server.listen(PORT);
console.log("server is listening on port: ", PORT);
Copy the code

result:

nodejsMiddleware proxies cross domains

NodeJS http-proxy-middleware implements cross-domain proxy. The principle is roughly the same as nginx, which starts a proxy server to realize data forwarding. You can also set the cookieDomainRewrite parameter to modify the domain name in the cookie in the response header to write cookies to the current domain for interface login authentication

WebSocketAgreement cross-domain

The WebSocket object provides an API for creating and managing WebSocket connections and for sending and receiving data over that connection. It is full duplex communication based on TCP, that is, the server and client can communicate bidirectionally and allow cross-domain communication. The basic protocols are WS ://(non-encrypted) and WSS ://(encrypted)

Page code examples:

function load() {
  let socket = new WebSocket("ws://localhost:3000");
  socket.onopen = function() {
    socket.send("message from index.html");
  };

  socket.onmessage = function(e) {
    console.warn("e.data");
    console.log(e.data);
  };
}
Copy the code

Node server code:

var express = require("express");
var WebSocket = require("ws");
var port = process.env.PORT || 3000;
var wss = new WebSocket.Server({ port: port });

wss.on("connection".function(ws) {
  ws.on("message".function(data) {
    console.log("----------data----------");
    console.log(data);
    console.log("----------data----------");

    ws.send("data from node");
  });
});
Copy the code