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
Cookie
、LocalStorage
和IndexDB
Unable to read.- Unable to obtain or manipulate another resource
DOM
. AJAX
The 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 is
GET
,POST
,HEAD
- The request header can only use the following fields:
Accept
The type of response content that the browser can accept.Accept-Language
A list of natural languages that browsers can accept.Content-Type
The type of the request is limited totext/plain
,multipart/form-data
,application/x-www-form-urlencoded
.Content-Language
The natural language that the browser wants to use.Save-Data
Whether 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¶ms=... ';
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 send
GET
Request, 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:
- https://localhost:3000/a.html
- https://localhost:3000/b.html
- 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.html
theiframe
Before loadingc.html
Page, at this pointc.html
Set upwindow.name = 'test'
.- in
c.html
After loading, setiframe
thesrc
forb.html
Because ofa.html
andb.html
In the codomain, andwindow.name
The 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:
- https://localhost:3000/a.html
- https://localhost:3000/b.html
- 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