This sharing is carried out by CPC Commercial Technology Department of Qutoutiao (Zhou Zhixiang – Mixed Yuan Thunderbolt player). Welcome to join qutoutiao with your resume at [email protected].

Do you know anything about cross-domains?

Understand (continue) Not understand (end)

Why cross-domain?

The cross-domain problem is caused by the restriction of the same Origin policy of the browser.

Why does the Browser set the Same Origin policy?

It’s because browsers are supposed to be safe. Without the same origin policy, browsers are vulnerable to attacks such as XSS and CSRF. (XSS and CSRF can be an additional knowledge point separately.) In this case, the operation of a web page under a domain name can directly obtain any information of another web page under a different domain name, or a web page can request interface data under different DNS servers at will.

What is the same origin policy?

The same origin policy is a convention that is one of the core security features of the browser. The same origin policy means that the protocol, domain name, and port are the same. If two same domain names point to the same IP address, they are not of the same origin. At the same time, the corresponding IP address printing is also non-homologous.

What are the limitations of the same origin policy?

DOM node

For DOM nodes, you can only operate the CONTENT of DOM nodes opened in the web page under the current domain name.

Store information

Cookie, sessionStorage, localStorage, indexedDB and other storage information cannot be obtained from different sources

An ajax request

For Ajax web requests, when the request is in a different domain, the browser will automatically intercept the error.

What are some examples of cross-domain scenarios?

If any protocol, domain name, or port number is different, the protocol is cross-domain. Also including (the difference between the level 1 domain name and the level 2 domain name) mutual request for resources is a cross-domain state.

Cross-domain address scenario diagram

In what way can cross-domain be resolved?

You can do it by JSONP

To load a resource from a browser, run the following command:

  1. img
  2. script
  3. link

These tags allow cross-domain loading of resources. Script tags in static HTML files under www.baidu.com domain name can load script resources under wwww.google.com server, etc.

By loading the understanding of cross-domain resources with the tags above, we can wrap the desired data from other domains.

How does JSONP work?

I’ve already laid the groundwork for the idea of principle. Let’s take advantage of script tags, which allow you to wrap data across domains and add resources.

The implementation process

// index.html // JSONP implementation simulationfunction 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 }let arrs = []
    for (let key in params) {
      arrs.push(`${key}=${params[key]}`)
    }
    script.src = `${url}?${arrs.join('&')}` document. The body. The appendChild (script)})} / / call way the json ({url:'http://localhost:3000/getUser',
  params: { name: 'peter' },
  callback: 'user'
}).then(data => {
  console.log(data)
})
Copy the code

Through the above code to achieve a basic JSONP call execution code.

  1. Declare a JSONP simulation function, the parameters passed in are the request address, request parameters, the front and back end of the convention wrapper function name, internal by returning promise mechanism to elegantly solve the data return method.

  2. Create a script temporary tag through the mechanism that script does not request resources across domains. Combine the address and parameters requested from the background into the form of a query parameter. Request address: http://localhost:3000/getUser? name=peter&callback=user

The key point is that wrapping the function name (key as callback, value as user) is a convention at the front and back ends.

  1. After final assemblyscriptTag inserted intodocumentIn the document, the browser will automatically request the address.

Background return result principle

// App.js returns the result app.get('/getUser'.function(req, res, next) {
  let { name, callback } = req.query
  console.log(name) // peter
  console.log(callback) // user
  res.send(`${callback}({
    code: 0,
    msg: 'Request successful',
    data: {
      id: 1234
    }
  })`)
});
Copy the code

The background is parsed with the Query parameter. If the returned result is an object, the object contains MSG message, request status code, data information data.

You may wonder why the value of the returned result is placed in a user execution function. This is the core principle of JSONP. Go back to this unexplained code snippet:

window[callback] = function(data) {
  resolve(data)
  document.body.removeChild(script)
}
Copy the code

Defines a function globally when executing its own encapsulated JSONP method. This function name is the function wrapper name of the front-end and back-end convention. The agreed global function is executed when the result is returned in the background. The above code snippet is executed, and the data parameter is returned via the resolve execution. Finally, delete the corresponding request script tag.

What is the difference between JSONP and AJAX?

Similarities:

JSONP is similar to Ajax in that the client initiates a request to the server.

Difference:

JSONP makes non-same-origin policy requests using Script tags, while Ajax makes same-origin policy requests.

The json pros and cons

Advantages:

The advantage of JSONP is that it is very compatible. This is because script tags can be used for non-same-origin requests. This is a basic feature of every browser.

Disadvantages:

The get request with only query parameters is supported, and the interaction mode has limitations. Also vulnerable to XSS attacks.

What if JSONP encapsulation is not supported in the background?

Can communicate through CORS network technology. (Full name is Cross-orgin Resource Sharing). For CORS, it also needs a coordination between the front and back ends. But the key is in the background configuration. You might think. That is, the background configuration, why the front also need to fully understand. Because no matter in the mode of production or development, cross-domain first has the largest impact on the front end, only a full understanding of the background can be expressed to the background to accurately set up and cooperate.

What background Settings should be suggested for a simple cross-domain request?

Foreground simulation setup

Create an index.html write request script locally. Start with http-server -p 4000 on local port 4000.

// index.html
let url = 'http://localhost:3000/getUser';
let xhr = new XMLHttpRequest();
xhr.open('get', url, true);
xhr.send();
Copy the code

Background simulation Settings

The express framework sets the request address, and the service starts on local port 3000.

// app.js
let express = require('express')
let app = express()

app.get('/getUser'.function(req, res) {
  res.send({
    code: 0,
    msg: 'Request successful',
    data: {
      id: 1234
    }
  })
})

app.listen(3000)
Copy the code

Browser returns results

Visit http://127.0.0.1:4000/index.html can through the Network, the console can see the browser http://localhost:3000/getUser service interface address the request to the background.

If Origin does not specify a licensed source, the server will return a normal HTTP response. The browser realizes that the response header does not contain the Access-Control-Allow-Origin field, and knows that something is wrong, throwing an error that is caught by XMLHttpRequest’s onError callback. Note that this error cannot be identified by the status code, because the status code for the HTTP response might be 200. The Status Code is 200 OK, but the response header does not return the expected value. Also found on the console:

Access to XMLHttpRequest at 'http://localhost:3000/getUser' from origin 'http://127.0.0.1:4000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Copy the code

CORS strategy to prevent from http://127.0.0.1:4000 to http://localhost:3000/getuser XMLHttpRequest: there is no ‘requested resource Access – control – Allow – origin ‘head.

This is one of the simplest CORS security policies, and it is obvious from the error that you need to tell the background to set the ‘access-Control-Allow-Origin’ header.

Background solution

// add app.use((req, res, next) => {res.setheader ('Access-Control-Allow-Origin'.The '*')
  // res.setHeader('Access-Control-Allow-Origin', req.headers.origin)
  next()
})

Copy the code

Do a layer of middleware filtering when a request is received, either way.

  1. Set the response header when returnedAccess-Control-Allow-Originfor*(Represents that all domain name requests to the current service are allowed to cross domain access)
  2. Set the response header when returnedAccess-Control-Allow-OriginIs the specified domain name. No other domain name allows a cross – domain access

Setting the access-Control-allow-Origin header solves all cross-domain problems.

The setting of the access-Control-Allow-Origin header can only handle simple cross-domain requests

Simple cross-domain request conditions:

Condition 1: Only the following request methods are allowed

  • GET
  • HEAD
  • POST

Condition 2: Content-type Specifies the allowed condition

  • text/plain
  • multipart/form-data
  • application/x-www-form-urlencoded

Condition 3: Do not exceed the following HTTP header fields

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID

What about other requests? What type of cross-domain request is it?

Other types of requests are called complex cross-domain requests. Complex cross-domain requests are made when they do not comply with the simple cross-domain request policy:

Complex cross-domain request interpretation:

  1. In addition to the simple cross-domain request method. Such asPUT,DELETE
  2. In addition to simple cross-domain requestContent-typeType. Such asapplication/json
  3. The custom ofheaderhead
  4. Under different domainscookietransmission

Try to solve several complex cross-domain cases

1. Request methods such as PUT and delete cause complex requests

// Change the request method -xhr.open ('get', url, true);
+ xhr.open('put', url, true);
Copy the code
App.put ('/getUser'// Omit... For the background just replace the GET request with a PUT to receive the requestCopy the code

The browser netWork does not send a PUT Request. The Request Method in General sends an OPIONS precheck Request.

At the same time, the browser will send an error message:

Access to XMLHttpRequest at 'http://localhost:3000/getUser' from origin 'http://127.0.0.1:4000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Copy the code

Solution:

// Add a new setting header to app.use // res.setheader ('Access-Control-Allow-Methods'.The '*')
res.setHeader('Access-Control-Allow-Methods'.'PUT')
Copy the code

The above sets which request methods are allowed to receive:

  • Set up the*, indicating that all request methods are allowed.
  • Sets the corresponding request methods to be comma-separated.

2. Content-type causes complex requests

+ xhr.setRequestHeader('content-type'.'application/json');
Copy the code

In the previous discussion of simple cross-domain request condition 2, there were only three content-Type types supported for simple cross-domain requests. Setting others will result in complex cross-domain requests. The same browser emits an error message when content-Type: application/json is set:

Access to XMLHttpRequest at 'http://localhost:3000/getUser' from origin 'http://127.0.0.1:4000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
Copy the code

An additional setting for the content-Type of complex cross-domain requests is required in the background:

// Add a new setting header to app.use + res.setheader ('Access-Control-Allow-Headers'.'content-type')
Copy the code

3. Custom headers cause complex requests

+ xhr.setRequestHeader('X-Customer-Header'.'value');
Copy the code

In the three simple cross-domain request criteria discussed earlier, all of them are custom headers, except for the HTTP headers above. Complex cross-domain requests are generated when the request is brought in, and the same browser issues an error message.

Access to XMLHttpRequest at 'http://localhost:3000/getUser' from origin 'http://127.0.0.1:4000' has been blocked by CORS policy: Request header field x-customer-header is not allowed by Access-Control-Allow-Headers in preflight response.
Copy the code

The same principle for the front set of the custom header, the background in the receiving time also allow to set the reception of the custom transmission of the header.

res.setHeader('Access-Control-Allow-Headers'.'content-type, X-Customer-Header')
// res.setHeader('Access-Control-Allow-Headers'.The '*')
Copy the code

When setting access-Control-allow-headers, you can separate multiple custom Headers with commas. * can also be passed, allowing any custom headers.

What about cookies in CROS?

In the case of absolute homology

In the case of absolute homology. When the foreground requests an interface or file from the background, it automatically puts cookies in the request header.

In the non-homotopic case

In the non-homotopic case. You need to use the CORS policy for transmission. Cookies are not carried into the request header by default, and request credentials need to be set to XHR.

xhr.withCredentials = true
Copy the code

Simple cross-domain requests with cookies

Set withCredentials = True if this is a simple cross-domain request. Cookie information will be put into the request header, and the background receives the request and sends it to the foreground. At this time, the browser can see that the data has been returned from the response, but it cannot obtain the data returned from the background, because it will be captured by the error of XHR, and the browser console will display the following prompt:

Access to XMLHttpRequest at 'http://localhost:3000/getUser' from origin 'http://localhost:4000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is ' ' which must be 'true' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
Copy the code

Complex cross-domain requests

Set withCredentials = True in case of complex cross-domain requests. An OPTIONS request is sent. The browser error message is still consistent with a simple cross-domain request error.

The solution

At this point, the foreground sends the cookie certificate, and the same background needs to agree to receive the certificate.

res.setHeader('Access-Control-Allow-Credentials'.true)
Copy the code

Reverse principle:

If the background agrees to receive the credentials. And the front desk does not set the case of sending credentials. Even if a cookie is set in the response header sent from the background to the foreground, simple cross-domain requests and complex cross-domain requests will result in invalid cookie padding. Check appliation/ Cookie and there is no cookie written by the background.

Maintain the Same Origin Policy

For safety reasons. Cookies essentially maintain the same origin policy pattern. Res.setheader (‘ access-Control-allow-origin ‘, ‘*’) cannot be set to *, Set res.setheader (‘ access-Control-allow-origin ‘, req.headers. Origin) to specify the source of the request.

Legal combination and illegal combination.

When setting the Credentials, the background needs to know the legal and illegal combinations of access-Control-allow. When the access-Control-allow-credentials parameter is set to true, the following parameters cannot be set to *. Otherwise, the following parameters are invalid.

  • Access-Control-Allow-Headers
  • Access-Control-Allow-Origin
  • Access-Control-Allow-Methods

How to get the information in the response header in XHR in CORS case?

This can be obtained using the xhr.getresponseHeader method. But this method only gets six basic fields: cache-Control, Content-Language, Content-Type, Expires, Last-Modified, and Pragma.

Custom headers and values can be inserted into the response headers during background responses.

res.setHeader('name'.'peter')
Copy the code

It can be seen in the message of the response body:

Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: content-type, X-Customer-Header
Access-Control-Allow-Methods: PUT
Access-Control-Allow-Origin: http://localhost:4000
Connection: keep-alive
Content-Length: 50
Content-Type: application/json; charset=utf-8
Date: Sun, 17 Feb 2019 08:18:08 GMT
ETag: W/"32-oUKytSTXnBL0hnySFj9PpHgmBQk"X-powered-by: ExpressCopy the code

Through the message, you can find a lot of information about the background Settings and the most important name header information. However, after testing the following methods, the conclusion is drawn:

xhr.onreadystatechange = function() {
  if (xhr.readyState === 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
      console.log(xhr.getResponseHeader('Content-Type'))
      console.log(xhr.getResponseHeader('name'))}}}Copy the code

After XHR returns successfully. Get two headers separately.

  • Content-type returns application/json; charset=utf-8

  • Name prompts an error message and returns null.

Refused to get unsafe header "name"// Reject unsafe header 'name'Copy the code

It is clear that, in addition to the above mentioned six types of header information can be retrieved, the rest of all need to be set up in the background to allow the response headers to access.

res.setHeader('Access-Control-Expose-Headers'.'name')
Copy the code

No error message is displayed in the browser and the value of name in the response header is printed. Notice If the value is *, it is invalid. The specified field header needs to be set.

Complex cross-domain requests cause each request to send an OPTIONS request. How to solve this problem?

From all of the above analysis of complex cross-domain requests, it is clear that those requests cause send prechecks, in a word, ** Access-Control-max-age Specifies the return result of the Preflight request (access-Control-allow-methods) And access-Control-allow-headers) for how long they can be cached. ** This is not friendly to request observation and request performance in the network. If you make it friendly and safe.

Make a time request validity period for precheck

res.setHeader('Access-Control-Max-Age', 600).Copy the code

Set an expiration time of 10 minutes for precheck requests (the time can be customized according to the project). But the cache time mechanism is different for each browser. When debugging locally, you will sometimes find that the expiration time set for precheck does not take effect. Note that the browser’s Disable cache may be enabled to cause this

How do I solve the cross-domain problem when the back-end Settings are unavailable during the front-end and back-end interworking?

Disable the cross-domain policy of the browser.

According to the previous analysis, the entire cross-domain pattern is caused by what the foreground browser is doing. For security, browsers perform a series of validations for cross-domain requests. Can you consider whether the fundamental problem can be solved by manually closing the browser cross-domain policy?

Mac creates a Chrome. Sh file

#! /bin/bash
#! /bin/sh
open -a "Google Chrome" --args --disable-web-security  --user-data-dir

exit 0
Copy the code

Run through a terminal:

Sh plus the chrome.sh file addressCopy the code

Note: Before running terminal commands, check whether Chrome has been started. If so, you need to manually shut down the entire Chrome process.

Successful results:

After entering the URL address. All cross-domain problems will be solved at once.

The principle of

The browser’s cross-domain policy has been turned off. There is no cross-domain behavior of browse send, because browsers intercept simple cross-domain requests and pre-check complex cross-domain requests.

By what is a simple cross-domain request intercepted?

To understand a simple cross-domain request, you need to understand the fields of the two request headers.

Request Origin in the request header

The Origin field in the request header indicates which site the request came from. This field only indicates the server name and does not contain any path information. This header is used for CORS requests.

In plain English, it tells the server that it is sent from that domain address. Origin appears in the request header only in the case of CORS.

Request HOST in the request header

The Host header specifies the domain name of the server (for virtual hosts) and, optionally, the TCP port number on which the server listens. If no port number is given, the default port of the requested service is automatically used (for example, port 80 is automatically used when requesting an HTTP URL). All HTTP/1.1 request messages must contain a Host header field. If an HTTP/1.1 Request lacks the Host header field or has more than one Host header field set, a 400 (Bad Request) status code is returned.

Generally speaking, it is the domain name of the server that is requested when the browser sends a request to the server.

Access-control-allow-origin in the response header

The response header specifies whether the resources of the response are allowed to be shared with the origin given in the foreground request header.

conclusion

So the cross-domain request is returned to the browser. The data will come back but. The browser checks whether Origin in the request header and Access-Control-Allow-Origin in the response header share a match, and if they do not. The browser’s XHR catches errors and throws them in the browser console. You don’t get the data you want.

How does a complex request browser detect cross-domain?

For complex cross-domain requests, once the browser detects the presence of a complex cross-domain request in the sent request header, it first sends a pre-request. The request header contains the following important contents:

  1. Access-Control-Request-Headers(If there are custom headers orcontent-typeThe class is not a simple request type.)
  2. Access-Control-Request-Method(Except for simple request methods)

And the request data and cookie information are not brought into the request information when the precheck request is sent.

What is a precheck request?

In CORS, a preflight request is made using the OPTIONS method to know if the server will allow the actual request. The use of precheck requests prevents cross-domain requests from having unexpected impacts on the server’s user data.

Request-header or request-method is sent in the browser request Header. In this case, the server needs to approve the corresponding information in the two request headers. The response header needs to be processed when the response returns. Access-control-allow-methods and access-Control-allow-headers need to be set.

Schematic diagram:

attachedCredentialsIs the request a simple cross-domain request or a complex cross-domain request?

The Credentials in CORS are fairly clear. But the point here is when the Credentials in XHR are set to true. This is a simple cross-domain request, no pre-check (OPTIONS) request will be sent, if this is a complex cross-domain request. A pre-check request is sent.

Therefore, whether the Credentials send prechecks depends on the determination of other request headers.

Schematic diagram:

Conclusion:

Only if the request header corresponds to the response return header. Allow each other to pass the sharing policy. No errors are caught for simple cross-domain requests. For complex cross-domain requests, real requests are sent. Transfer data, such as cookies, is also carried into the request body. Therefore, disabling the browser cross-domain policy means that the browser will not catch the Origin header match and will also disable the corresponding OPTIONS precheck request. Directly send to the corresponding background server. So essentially there is cross-domain, but the server is always returning data. Any errors or failure to send a real request are the result of the browser’s security mechanisms.

How to solve cross domain through proxy hijacking mechanism?

We’ve seen that browsers have cross-domain problems with requests to the server, but servers do not have cross-domain problems with requests to the server. After request hijacking through the MIDDLE Server (MS), the server sends the request to the server and then returns the request to the browser.

Schematic diagram:

The Node service hosts the project through scaffolding startup in each frame. For example, IN VUE-CLI, HTTP-proxy-middle is used to intercept a request by proxy and send a request to the target server to solve cross-domain problems.

// Enable port 3000 via express // index.html <script>let url = '/api/getUser';
  let xhr = new XMLHttpRequest();
  xhr.open('post', url, true);
  xhr.setRequestHeader('content-type'.'application/json');
  xhr.setRequestHeader('X-Customer-Header'.'value');
  xhr.send();
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        console.log(1)
        console.log(xhr.response)
      }
    }
  }
  
</script>


const proxyOption = {
	target: 'http://localhost:4000',
	pathRewrite: {
        '^/api/' : '/'// Override request, API/parse as /}, changeOrigin:true
};

app.use('/api', proxy(proxyOption))

Copy the code
// Start 4000 port app.post('/getUser', (req, res, next) => {
  res.send({
    code: 1
  })
})
Copy the code

When a static file on port 3000 sends an Ajax request, it is in a domain name itself and does not cause any cross-domain problems. At the same time, it will be captured and intercepted by app.use(‘/ API /’) and the URL will be rewritten to send the request to server 4000. This is the server-side request communication. When the server on port 4000 receives the request, the data is returned to the server on port 3000, and then returned to the ajax of the request.

How to do this with node’s native API?

app.use('/api', (req, res) => {
  const reqHttp = http.request({
    host: '127.0.0.1',
    path: '/getUser',
    port: '4000',
    method: req.method,
    headers: req.headers
  }, (resHttp) => {
    let body = ' '
    resHttp.on('data', (chunk) => {
      console.log(chunk.toString())
      body += chunk
    });
    resHttp.on('end', () => {
      res.end(body)
    });
  })
  reqHttp.end()
});
Copy the code

The above code essentially simulates the way of proxy hijacking. At the same time, after intercepting the request beginning with/API, the node sends the request to the corresponding background through the request method of the original HTTP module. At the same time, some request body and request data requested by the browser are transmitted to the server. The data is finally returned to the client browser through the end method that the HTTP module listens for. In this way, a quadratic transformation method is formed to solve cross-domain problems. The whole is to take advantage of the server to send requests to the service without cross-domain policy restrictions, which is called the same origin policy. The browser does pre-check for options, but the server does not.