Welcome to Star at Github

While developing high-performance Web applications, the security mechanism of browsing should not be ignored. The following is to share the same origin policy and cross-domain solutions of browsers.

This article will follow the following mind map to explain.

Browser Same-origin Policy

The definition of the source

Origin refers to the part of a URL that consists of the protocol, host name, and port.

Now that we know the definition of source, what is homology?

We at http://store.company.com/dir/page.html as an example, and comparing the url form below.

URL The results of why
http://store.company.com/dir2/other.html homologous Only the path is different
http://store.company.com/dir/inner/another.html homologous Only the path is different
https://store.company.com/secure.html Is not the source Different protocols have different ports
http://store.company.com:81/dir/etc.html Different source Different ports
http://news.company.com/dir/other.html Different source The host different

The default HTTP port is 80 and HTTPS port is 443

Restrictions on the same-origin policy

  • CookieLocalStorageIndexDBUnable to read.
  • Unable to obtain or manipulate another resourceDOM.
  • AJAXThe request could not be sent.

Request a cross-domain solution

Cross-domain requests communicate with the server through HTTP. Common schemes are as follows:

  • CORS
  • JSONP
  • Websocket
  • Request broker

CORS

Cross-origin Resource Sharing Is the full name of CORS.

Is a cross-domain mechanism that the browser sets up for AJAX requests to be accessed across domains if the server allows it. The HTTP response header is used to tell the browser server whether to allow cross-domain access by scripts in the current domain.

Cross-domain resource sharing divides AJAX requests into two categories:

  • A simple request
  • Non-simple request

A simple request

A simple request must meet the following characteristics

  • The request method isGET,POST,HEAD
  • The request header can only use the following fields:
    • AcceptThe type of response content that the browser can accept.
    • Accept-LanguageA list of natural languages that browsers can accept.
    • Content-TypeThe type of the request is limited totext/plain,multipart/form-data,application/x-www-form-urlencoded.
    • Content-LanguageThe natural language that the browser wants to use.
    • Save-DataWhether the browser wants to reduce the amount of data transferred.

The simple request flow is as follows

When the browser sends a simple request, it adds an Origin field to the header of the request, which corresponds to the source information of the current request.

After receiving a request, the server checks the Origin field in the request header and returns the corresponding content.

After receiving a response packet, the browser checks the access-Control-allow-Origin field in the response header. The value of this field is the source of cross-domain requests allowed by the server. The wildcard * indicates that all cross-domain requests are allowed. If the header does not contain the access-Control-allow-Origin field or the response header field access-Control-allow-Origin does not Allow the request from the current source, an error will be thrown.

Non-simple request

If the request does not meet the characteristics described above, it becomes a non-simple request. When a non-simple request is processed, the browser sends a Preflight request. This precheck Request is OPTIONS and adds a Request header field access-Control-request-method, which is the Request Method used in the cross-domain Request.

In addition to the access-Control-allow-Origin field in the response header, at least the access-Control-allow-methods field is added to tell the browser server which request Methods are allowed. And return 204 status code.

The server also responds with an Access-Control-allow-headers field based on the browser’s Access-Control-request-HEADERS field to tell the browser what Request header fields the server allows.

After the browser gets the header field of the precheck request response, it determines whether the current request server is within the scope of the server’s permission. If so, it continues to send the cross-domain request, and if not, it directly reports an error.

CORS commonly used header fields

  • origin

The Origin field indicates which site the request is from, including the protocol, domain name, port number, and not including the path. Without credentials, the Origin field can be a *, indicating that the request accepts any domain name

  • Access-Control-Allow-Origin

The response header, which identifies which domain requests are allowed

  • Access-Control-Allow-Methods

The response header identifies which request methods are allowed

  • access-control-allow-headers

The response header, used in the precheck request, lists the request headers that will be allowed in the formal request.

  • Access-Control-Expose-Headers

The response header tells the browser which fields the server can customize to expose to the browser

  • Access-Control-Allow-Credentials

The Credentials can be cookies, Authorization headers, or TLS Client certificates.

  • Access-Control-Max-Age

Precheck the cache duration of requests

The sample code

Take Express as an example:

// Express based middleware setup
const express = require('express')
const app = express();
app.use((req, res, next) = > {
    if(req.path ! = ='/' && !req.path.includes('. ')) {
        res.set({
            'Access-Control-Allow-Credentials': true.'Access-Control-Allow-Origin': req.headers.origin || The '*'.'Access-Control-Allow-Headers': 'X-Requested-With,Content-Type'.'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS'.'Content-Type': 'application/json; charset=utf-8'
        })
    }
    req.method === 'OPTIONS' ? res.status(204).end() : next()
})

// Third party open source package to implement
const app=express();
let cors=require("cors");
app.use(cors()); 
Copy the code

JSONP

JSONP (JSON with Padding) just means to fill it with JSON data.

How do you fill it?

The way it is implemented is to fill the JSON number into a callback function. Using script tag to reference JS files across domains is not restricted by the same origin policy of the browser, with natural cross-region.

Suppose we want to request data from http://www.b.com in http://www.a.com.

1. Globally declare a function fn that handles the return value of the request.

function fn(result) {
  console.log(result)
}
Copy the code

2. Add the function name and other parameters to the URL.

let url = 'http://www.b.com?callback=fn&params=... ';
Copy the code

3. Dynamically create a script tag and assign the URL to the script SRC property.

let script = document.createElement('script');
script.setAttribute("type"."text/javascript");
script.src = url;
document.body.appendChild(script);
Copy the code

4. After receiving the request, the server parses the URL parameters and performs corresponding logical processing. After obtaining the result, the URL is written as a callback function and returned to the browser.

fn({
  list: [],... })Copy the code

5. After the browser receives the RETURNED JS script, it immediately executes the file content and obtains the data returned by the server.

Although JSONP implements cross-domain requests, it also has the following problems:

  • Can only sendGETRequest, limiting parameter size and type.
  • The request process cannot be terminated, which makes it difficult to process timeout requests on weak networks.
  • Unable to catch the exception message returned by the server.

Websocket

Websocket is an application layer full-duplex protocol proposed by THE HTML5 specification, which is suitable for real-time communication between browser and server.

A term used for full-duplex communication transmission, where “work” refers to the direction of communication.

Duplex means that the communication between the client and the server and between the server and the client can be both directions. Full means that the communication parties can simultaneously send data to each other. This corresponds to half-duplex, where both parties can send data to each other but not simultaneously, and simplex, where data can only be sent from one party to the other.

Here is a simple sample code. Create a WebSocket connection directly on website A, connect to website B, and then call the send() function of WebScoket instance WS to send a message to the server, and listen to the onMessage event of instance WS to get the response content.

let ws = new WebSocket("ws://b.com");
ws.onopen = function(){
  // ws.send(...) ;
}
ws.onmessage = function(e){
  // console.log(e.data);
}
Copy the code

Request broker

We know that the browser has the same origin policy security restrictions, but the server does not, so we can use the server for request forwarding.

Taking Webpack as an example, webpack-dev-server is used to configure the proxy. When the browser initiates a request with a prefix of/API, the request will be forwarded to http://localhost:3000 server, and the proxy server will get the response and return it to the browser. The browser is still requesting the current site, but it has actually been forwarded by the server.

// webpack.config.js
module.exports = {
  / /...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000'}}};// Use Nginx as the proxy server
location /api {
    proxy_pass   http://localhost:3000;
}
Copy the code

Page cross domain solution

In addition to cross-domain requests, there are cross-domain requirements between pages, such as communication between parent and child pages when using iframe. Common schemes are as follows:

  • postMessage
  • document.domain
  • window.name(not often used)
  • location.hash + iframe(not often used)

postMessage

Window. postMessage is a new HTML5 function that allows parent pages to communicate with each other, regardless of whether the two pages are identical or not.

Take https://test.com and https://a.test.com for example:

// https://test.com
let child = window.open('https://a.test.com');
child.postMessage('hello'.'https://a.test.com');
Copy the code

The above code opens the child page with the window.open() function and then calls the child.postMessage() function to send string data hello to the child page.

In the child page, you simply listen for the Message event to get the parent page’s data. The code is as follows:

// https://a.test.com
window.addEventListener('message'.function(e) {
  console.log(e.data); // hello
},false);
Copy the code

The postMessage() function is called through the window.opener object when a child page sends data.

// https://a.test.com
window.opener.postMessage('hello'.'https://test.com');
Copy the code

document.domain

The domain property returns the domain name of the server from which the current document was downloaded. Change the value of document.domain to cross domains. This case is suitable for pages with the same main domain name and different subdomain names.

We at https://www.test.com/parent.html, there is an iframe on this page, and the SRC is http://a.test.com/child.html.

At this time as long as the https://www.test.com/parent.html and http://a.test.com/child.html these two page of the document. The domain are set to the same domain name, Parent-child pages can then communicate across domains and share cookies.

But note that only the document. The domain is set to more advanced parent domain have the effect, for example, in http://a.test.com/child.html, can be in the document. The domain is set to test.com.

window.name

The name attribute sets or returns a string of the name of the hosting window. The name value persists after different pages (including domain name changes) load.

We prepare three pages:

  1. https://localhost:3000/a.html
  2. https://localhost:3000/b.html
  3. https://localhost:4000/c.html

Page A and page B are in the same domain, and page C is in another domain.

If we want a and C to communicate with each other, it must involve cross-domain communication, so we can change the value of window.name to achieve cross-domain communication.

Overall implementation idea, B.HTML is actually just an intermediate proxy page.

  • a.htmltheiframeBefore loadingc.htmlPage, at this pointc.htmlSet up window.name = 'test'.
  • inc.htmlAfter loading, setiframethesrcforb.htmlBecause ofa.htmlandb.htmlIn the codomain, andwindow.nameThe value does not change after the domain name change page is reloaded to achieve cross-domain.

a.html

<! -- https://localhost:3000/a.html -->

<! DOCTYPEhtml>
<html lang="en">
<head></head>
<body>
    <iframe src='https://localhost:4000/c.html' onload="onload()" id="iframe"></iframe>
    <script>
        // Iframe is called after loading to prevent SRC changes from occurring in an endless loop.
        let first = true
        function onload() {
            if (first) {
                let iframe = document.getElementById('iframe')
                iframe.src = 'https://localhost:3000/b.html'
                first = false
            } else {
                console.log(iframe.contentWindow.name) // 'test'}}</script>
</body>
</html>
Copy the code

c.html

<! -- https://localhost:4000/c.html -->
<! DOCTYPEhtml>
<html lang="en">
<head></head>
<body>
    <script>
        window.name = 'test'
    </script>
</body>
</html>
Copy the code

location.hash

The hash attribute is a readable and writable string that is the anchor part of the URL (starting with the # sign).

We prepare three pages:

  1. https://localhost:3000/a.html
  2. https://localhost:3000/b.html
  3. https://localhost:4000/c.html

Page A and page B are in the same domain, and page C is in another domain.

If we want a and C to communicate with each other, it must be cross-domain, which is achieved by changing the value of window.location.hash in the following code.

a.html

<! DOCTYPE html><html lang="en">
<head></head>
<body>
    <! Hash to c.html -->
    <iframe src='https://localhost:4000/c.html#test' id="iframe"></iframe>
    <script> 
        // Listen for hash changes
        window.addEventListener('hashchange'.() = >{
            console.log(location.hash)
        })
    </script>
</body>
</html>
Copy the code

b.html

<! DOCTYPE html><html lang="en">
<head></head>
<body>
    <script>
     // Window. Parent is the c page
     // The parent of page C is page A, then set the hash value of page A
      window.parent.parent.location.hash =  location.hash
    </script>
</body>
</html>
Copy the code

c.html

<! DOCTYPE html><html lang="en">
<head></head>
<body>
    <script>
        console.log(location.hash)
        let iframe = document.createElement('iframe')
        iframe.src = 'https://localhost:3000/b.html#test_one'
        document.append(iframe)
    </script>
</body>
</html>
Copy the code

conclusion

When requesting resources across domains, CORS and JSONP are recommended.

PostMessage and document.domain are recommended when page resources cross domains.

Refer to the link

  • mdn CORS
  • Ruan Yifeng cross-domain resource sharing CORS details
  • The same origin policy of the browser