This article introduces cross-domain problems caused by browser same-origin policy and practices in development and production environments. Most of the concepts came from teacher Ruan Yifeng’s post. I act as a brick moving role, do a summary and practice.

background

With the popularity of front-end frameworks vUE and React in MVVM mode, the development mode of separating the front and back ends is more common. The front end only needs to obtain the data returned by the back end through RESTful apis to complete all page logic. When the front-end and back-end services are developed separately, they may belong to different [sources]. However, due to the same origin policy of the browser, there are various problems in the interaction between the front-end and back-end services in the actual development and production environment.

Browser Same-origin policy (same-origin policy)

As mentioned above, the existence of the same origin policy in the browser has caused some confusion in the way of the development of the front and back end separation, which hinders the normal development process. What is the same origin policy?

In layman’s terms, the same origin policy restricts how documents or scripts loaded from the same source interact with resources from another source. This is an important security mechanism for isolating potentially malicious files.

homologous

Homology refers to triad:

  • The agreement is the same
  • Domain name is the same
  • The same port

For example, http://www.example.com/dir/page.html, the agreement is http://, a domain name is www.example.com, port is 80 (the default port can be omitted). Its homology is as follows.

  • www.example.com/dir2/other….
  • Example.com/dir/other.h…
  • V2.www.example.com/dir/other.h…
  • www.example.com:81/dir/other.h…
  • www.example.com/dir/other.h…

Application scenarios of the same Origin policy

The purpose of the same origin policy is to ensure the security of user information and prevent malicious websites from stealing data.

Consider this situation: Site A is A bank, and the user logs in and then goes to another site. What happens if other websites can read the cookies of WEBSITE A?

Obviously, if a Cookie contains privacy (such as the amount of money deposited), this information will be disclosed. What’s more, cookies are often used to save the user’s login status. If the user does not log out, other websites can pretend to be the user and do whatever they want. Because browsers also specify that submitting forms is not subject to the same origin policy.

Therefore, the same origin policy is necessary, otherwise cookies can be shared and the Internet is not secure at all.

Scope of influence (cross – domain problem) and avoidance method

The same origin policy ensures basic security in the network environment. If the policy is violated, cross-domain problems occur

scope

By introducing homology restrictions that are necessary for safety reasons, legitimate uses may also be limited. If it’s not homologous, there are three behaviors that are limited

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

Avoid methods

In the case of cross-domain, there will be different ways to deal with the restrictions of the above three behaviors. The specific ways are as follows

Different window

Two different Windows (referring to two different tabs and different browser Windows) can overcome the cross-domain problem only when the first level domain name of two pages is the same and the second level domain name is different, browsers allow document.domain to share cookies.

For example, A web site is http://w1.example.com/a.html, B web is http://w2.example.com/b.html, so as long as the same document. The same domain, two pages can share A Cookie.

document.domain = 'example.com';
Copy the code

Now, page A sets A Cookie through the script:

document.cookie = 'test1=hello';
Copy the code

Then web page B can read this Cookie:

const allCookie = document.cookie;
// test1=hello
Copy the code

Note that this method only applies to Cookie and IFrame Windows. LocalStorage and IndexDB cannot circumvent the same origin policy by using this method and instead use the PostMessage API described below.

In addition, the server can specify the domain name of the Cookie as the level-1 domain name, for example,.example.com.

Set-Cookie: key=value; domain=.example.com; path=/
Copy the code

In this case, the second level domain and the third level domain can read this Cookie without setting anything.

Can’t get each other’s DOM elements under different Windows anyway

The same window

If two different sources are under the same window (i.e. the same browser window, opened by iframe and window.open methods), they cannot communicate with the parent window.

Of course, you can’t get the DOM element of the iframe child from the parent window:

document.getElementById("iframeId").contentWindow.document;
// Uncaught DOMException: Blocked a frame from accessing a cross-origin frame.
Copy the code

< span style = “box-width: border-box; color: transparent; color: transparent;

window.parent.document.body
/ / an error
Copy the code

If the two Windows have the same level 1 domain but different level 2 domain names, you can circumvent the same-origin policy by setting the Document.domain property described in the previous section and retrieve the DOM.

For sites with completely different sources, there are three ways to solve the cross-domain window communication problem in this case:

  1. Segment identifier (fragment identifier)
  2. window.name
  3. Cross-document communication API (Cross-document messaging)
Segment identifier (fragment identifier)

Fragment identifier (fragment identifier) mean, # # at the back of the part of the URL, like http://example.com/x.html#fragment # fragments. If you just change the fragment identifier, the page does not refresh.

The parent window can write information to the fragment identifier of the child window:

const src = `${originUrl}#${data}`;
document.getElementById('iframeId').src = src; 
Copy the code

The child window is notified by listening for the Hashchange event:

window.onhashchange = () = > {
  const message = window.location.hash;
  // ...
}
Copy the code

Similarly, child Windows can also change the fragment identifier of the parent window:

parent.location.href = `${target}#${hash}`
Copy the code
window.name

The browser window has the window.name attribute. This property can be obtained directly across domains, as long as the previous page in the same window sets the property, and subsequent pages can read it.

The parent window opens a child window that loads a 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:

const data = document.getElementById('iframeId').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.

Cross-document communication API (Cross-document messaging)

The above two methods are hack methods. In order to solve this problem, HTML5 introduces a new API: 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.

For example, the parent window http://aaa.com sends a message to the child window http://bbb.com by calling the postMessage method:

const popup = window.open('http://bbb.com'.'title');
popup.postMessage("Hello Word!".'http://bbb.com');
Copy the code

The first argument to the postMessage method is the specific message content, and the second argument is the origin of the window that received the message, i.e. “protocol + domain + port”. You can also set this parameter to *, which indicates that the domain name is not limited and the message is sent to all Windows.

A child window sends a message to its parent window in a similar way:

window.opener.postMessage('Nice to see you'.'http://aaa.com');
Copy the code

Both parent and child Windows can listen for messages from each other via message events:

window.addEventListener('message'.function(e) {
  console.log(e.data);
},false);
Copy the code

The event object for the message event provides the following three properties:

  • event.source: Window for sending messages
  • event.origin: Address to which the message is sent
  • event.data: Message content

Here is an example where a child window references the parent window via the event.source property and then sends a message:

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  event.source.postMessage('Nice to see you! '.The '*');
}
Copy the code

The event.origin attribute filters messages that are not sent to this window:

window.addEventListener('message', receiveMessage);
function receiveMessage(event) {
  if(event.origin ! = ='http://aaa.com') return;
  if (event.data === 'Hello World') {
      event.source.postMessage('Hello', event.origin);
  } else {
    console.log(event.data); }}Copy the code

Of course, if the postMessage parent window can communicate with each other, then the parent window can call each other’s resources such as LocalStorage and IndexDB to achieve cross-domain evasion.

AJAX (emphasis)

There are several solutions to the same origin policy’s restrictions on AJAX requests

  1. JSONP
  2. WebSocket
  3. Cross-domain resource sharingCORS
  4. The agentProxy

Setting CORS is a common solution

JSONP

The principle of JSONP is to make use of SRC attributes of tags such as IMG and Script to send requests to the server, which is not restricted by the same origin policy. After the server receives the request, the data is returned by the parameters of a callback function agreed upon. A way of interacting with the server that the data returned by the server can be executed directly in the global context after setting the script tag type=”text/script”.

  • javascriptDynamically createscriptTag, SettingssrcTo send requests across source urls:
    // Dynamically add script tags
    function addScriptTag(src) {
      var script = document.createElement('script');
      script.setAttribute("type"."text/javascript");
      script.src = src;
      document.body.appendChild(script);
    }
    
    window.onload = function () {
      // The address specifies the convention's callback function name callback=foo
      addScriptTag('http://example.com/ip?callback=foo');
    }
    // The convention of callback functions is pre-defined in the front end
    function foo(data) {
      console.log('Your public IP address is: ' + data.ip);
    };
    Copy the code
  • The server receiveshttp://example.com/ip?callback=fooAfter the request, placing the data in the argument position of the callback function returns:
    foo({
      "ip": "x.x.x.x"
    })
    Copy the code
  • The data returned by the server is immediately executed by the browser and can be directly printed out because the specified functions are predefined in the front endip.

The above steps complete a simple JSONP interaction process. Obviously, it can effectively bypass the same-origin policy to realize the data interaction between the front and back ends. However, from the perspective of the modern front-end, it is just a hack method, and there are many problems:

  • This mode is supported onlyGETRequests, also mentioned here, can be made through nulliframeandformForm implementationPOSTI won’t go into details here
  • This method is a little complicated and can not adapt to the multi-interaction application scenarios of modern applications
  • Of course, this approach effectively bypasses the same origin policy but also exposes security issues
WebSocket

WebSocket is a communication protocol that prefixes ws:// (unencrypted) and WSS :// (encrypted). This protocol does not enforce the same origin policy and can be used for cross-source communication as long as the server supports it.

Cross-domain resource sharing (CORS(Key points)

CORS stands for Cross-Origin Resource Sharing. It is a W3C standard and a fundamental solution for cross-source AJAX requests. Whereas JSONP can only make GET requests, CORS allows any type of request.

  • CORSAllows browsers to send across domainsXMLHttpRequestRequest, to overcomeAJAXHomologous restriction of
  • CORSRequires both browser and server support,IEBrowser cannot be lower thanIE10
  • CORSThe whole process is insensitive to the user and the developer, and once the setup is complete, the browser will discover itAJAXWhen the request is cross-domain, additional headers are automatically added, and sometimes an additional precheck request is sent:
    • Simple Request

      It is a simple request as long as the following conditions are met:

      1. A request is one of three ways:
        • HEAD
        • GET
        • POST
      2. HTTP headers do not exceed the following fields:
        • Accept
        • Accept-Language
        • Content-Language
        • Last-Event-ID
        • Content-type: Application/X-www-form-urlencoded, multipart/form-data, text/plain

      For simple requests that meet the above conditions, the browser automatically adds the Origin field to the request header to specify the source address to send the cross-domain request:

      The server determines whether to approve the request based on the source specified by Origin:

      You can see a normal one in the screenshot aboveCORSThe return header contains some attachment information:

      • Access-Control-Allow-Origin: Corresponding to the Origin field, if Origin is not licensed by the service, a normal HTTP response (excluding access-Control-Allow-Origin) will be returned. The browser will throw an error message if it finds no qualified headers:

        Of course, the server can set the request timeOriginField value, or just give one*To identify requests to accept arbitrary domain names.

      • Access-control-allow-credentials: This field is optional and indicates whether cookies are allowed to be sent. Cookies are not included in CORS requests by default. Setting this to true explicitly means that cookies can be included in the request. In this case, the browser-side code also needs to set withCredentials = true to ensure that cookies are allowed in the requests. In this case, the server automatically adds the access-Control-allow-credentials: true header

        / / native XMLHttpRequest
        const xhr = new XMLHttpRequest();
        xhr.withCredentials = true;
        
        // umi-request
        import { extend } from 'umi-request';
        const request = extend({
          // other config...
          credentials: 'include'.// Whether cookies are included in the request by default
        });
        
        // axios
        import axios from 'axios';
        const instance = axios.create({
          // 'withCredentials' indicates whether credentials are needed for cross-domain requests
          withCredentials: true.// default false
        })
        Copy the code

        To send cookies, access-Control-allow-origin cannot be set to *. You need to set the domain name that is consistent with the requested web page.

      • Access-Control-Expose-Headers: In AJAX 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:

        Access-control-expose-headers = access-Control-expose Headers = access-Control-expose Headers Content-disposition: Attachment; Content-Type, access-Token,Authorization; content-disposition: Attachment; content-disposition: Attachment; filename=%E6%B6%89%E6%8B%90%E6%A1%88%E4%BB%B6.xlsxCopy the code

        Then I can get the file name in Content-Disposition to download when I return the Blob file stream on the back end:

        import { extend } from 'umi-request';
        const request = extend({
          credentials: 'include'.// Whether cookies are included in the request by default
          // The setup can use getResponse()
          getResponse: true}); request.use(async (ctx, next) => {
          await next();
        
          const { data: resData, response } = ctx.res;
        
          if(! resData.code) {// If it is blob, get the corresponding file name
            ctx.res = {
              blob: response.blob(),
              filename: decodeURIComponent(
                // example: attachment; filename=%E6%B6%89%E6%8B%90%E6%A1%88%E4%BB%B6.xlsx
                (response.headers.get('Content-disposition') | |' ').split('=') [1] | |'Unnamed',),};return; }}Copy the code
      • Access-control-allow-methods: This field indicates all cross-domain request Methods supported by the server, and returns all supported Methods rather than the Methods currently requested by the browser, as well as avoiding multiple pre-check requests for non-simple requests mentioned below.

        Access-Control-Allow-Methods: OPTIONS,HEAD,DELETE,GET,PUT,POST
        Copy the code
      • Access-control-allow-headers: specifies all header fields supported by the server.

        Access-Control-Allow-Headers: Content-Type,Access-Token,Authorization
        Copy the code
    • Non-simple Request (not-so-simple Request)

      The definition of a simple request is defined above, so a case that does not satisfy a simple request is a non-simple request. Requests that have special requirements on the server, such as PUT, DELETE, or content-type application/ JSON.

      The browser sends an OPTIONS request, called a preflight, after determining that the request is not a simple one 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.

      Above is the request header for a precheck request, the point beingAccess-Control-Request-HeadersandAccess-Control-Request-MethodTwo headers

      • Access-Control-Request-Headers: Indicates the header information field to be sent, as shown in the screenshot aboveContent-Type
      • Access-Control-Request-Method: Indicates what the precheck request will useHTTPMethod, shown in the screenshot abovePOST

      The figure above shows the header of a precheck request. The general information is the same as the simple request. You can also add an Access-Control-max-age (field optional) information to indicate the validity period of the precheck.

      After the precheck request passes, the browser automatically sends a normal request with the same request and response headers as the simple request.

Whether a CORS request is a simple request or a non-simple request, the general idea is to determine whether the cross-domain request is trusted through Origin, set the trusted source information, header information, method information verification can be carried out after the normal HTTP cross-domain request interaction

Proxy
  • Agent (Proxy) is to bypass the request sent by the front end by setting up a proxy server to forward itThe same-origin policyThe purpose of.
  • The front end is deployed toNginx.Caddy.nodejsAnd other static services, which are then forwarded to the destination server through proxy configuration to achieve cross-domain data interaction.
  • This method is commonly used in front-end developmentwebpackthedevServerTo implement proxies to make development smoother.
  • NginxProxy implementation schematic:
    http {
      location / {
        proxy_pass http://x.x.x.x:8005
      }
    }
    Copy the code

    With the above configuration, a proxy forward can be implemented, of courselocationNavigate to any path you want to forward, or configure other proxy options

Development of CORS request some error information and interpretation

We spent a lot of time on the same origin policy, cross-domain, and how to solve these problems in development and production environments. Finally, we found a common solution: CORS.

Common CORS error message:

  • CORSNormal request verification fails:Usually in the form ofOriginandAccess-Control-Allow-OriginIs not set or inconsistent, or is reported when the back-end service is being restarted at the time of the front-end requestCORS error

  • CORSNormal request verificationHeadersNot by:Customizations are usually not set for non-simple requestsHeaderOr inconsistent, the backend needs to set the corresponding field, the above error refers toauthorizationThis field does not pass the same origin policy

  • CORSFailed to check the precheck request:

Error cause:

  • The response header information of simple request, non-simple request and precheck request is analyzed above, which is usually implemented at the front and back endsCORSThe browser verification fails because key Settings are missing
  • The fields commonly involved includeOrigin.Access-Control-Allow-Origin.Access-Control-Allow-Methods.Access-Control-Allow-Credentials.Access-Control-Allow-HeadersThe reason for this critical verification information

CORS solution

The setup and configuration of CORS is mentioned sporadically above, and a complete example is posted here:

  • Back end (Java)As can be seen from the figure above, roughly the definition of a similarFilterTo unify requests:

    • According to comingOriginTo set upAccess-Control-Allow-Origin, if notOriginIs set*
    • Set up theAccess-Control-Allow-Credentialsfortrue
    • Set up theAccess-Control-Allow-MethodsforOPTIONS,HEAD,DELETE,GET,PUT,POST
    • Set up theAccess-Control-Allow-HeadersforContent-Type,Access-Token,Authorization
    • Set up theAccess-Control-Expose-HeadersforContent-disposition,Content-Type,Access-Token,Authorization
    • Found to beOPTIONSThe precheck request is passed and returned normally
  • Front end (javascript)Front-end configuration is relatively simple, as shown aboveumi-requestAs an example, you can directly set whether to carry this parameterCookieAnd whether it can be obtainedresponseHeader fields

Through the above configuration which can smoothly complete the development and production environment of AJAX cross-domain interaction, of course there are exceptions, such as the implementation Blob binary stream data download files, still will have some interface to CORS error, its reason is the back-end engineer to avoid head information reset file cache problem, leading to loss of CORS relevant header information, Therefore, back-end engineers need to filter out the corresponding header information and reset the interface to solve the problem that files cannot be downloaded.

conclusion

This paper explores the same-origin policy, cross-domain problems, and solutions in depth, mentions the ways of data sharing and interaction restricted by the same-origin policy, and also gives corresponding solutions. With the development of browser, front-end and back-end technologies, some new methods will become more common. For example, cross-domain resource sharing CORS is used to solve the data interaction problems at the front and back ends, instead of some solutions to cross-domain problems implemented by various mechanisms hack. CORS is more flexible and convenient, but we still frequently encounter CORS errors. Here we find information about CORS errors in Firefox. Through this error message and the CORS interaction process introduced above to solve the CORS error message is very helpful, as long as you grasp the request header information can find the root cause of the problem.

To move the brick

  • w3c-same-origin policy
  • Segmentfault – Stop asking me cross-domain questions
  • Ruan Yifeng – Browser same origin policy and its circumvention method
  • Ruan Yifeng – Cross-domain resource sharing CORS details
  • MDN-same-origin policy
  • MDN-CORS errors
  • Zhang Xuxin – Cross-domain in Canvas