The word “cross-domain” sticks like a sticking plaster to every front-end developer, whether you’re on the job or in an interview. In order to prepare for the interview, I would memorize several random plans every time. I don’t know why I do this, but I can throw them away after the reverse. I don’t think I will use so many random plans in my work. When it comes to the real work, the development environment will be done by Webpack-dev-server, and the big guys on the server side will also be matched. I don’t care what is matched, but it won’t cross domains. So the days went by, and finally one day I decided I couldn’t go on like this any longer. I had to get to the bottom of this thing! Hence the article.

To master cross-domain, first of all, why does cross-domain exist

Indeed, we this kind of brick workers is to mix food to eat, a good interface to tell me across the domain, this obstruction we easily move the brick thing is disgusting! Why cross domains? Who’s doing this? To find out who caused this problem, click on the same Origin policy for browsers.

It’s hard to understand the official stuff, but at least you know it, because the same origin policy of the browser causes cross-domain, and that’s what the browser is doing.

So why do browsers mess up? Just don’t want to give us good days? In response to this query, the browser said, “The same origin policy restricts how documents or scripts loaded from the same source can interact with resources from another source. This is an important security mechanism for isolating potentially malicious files.”

It’s hard to understand the official language, but it doesn’t matter, at least you know, it seems to be a security mechanism.

So why do we need such a security mechanism at all? What problem does such a security mechanism solve? Don’t worry. Let’s get on with our research.

Two dangerous scenarios without the same-origin policy restriction

As FAR as I know, browsers do this same origin policy from two aspects: one is for interface requests and the other is for Dom queries. Imagine the danger of both of these actions without such restrictions.

The interface request is not restricted by the same origin policy

There’s a little thing called a cookie that you probably know, and it’s used for things like login, and the purpose is to let the server know who made the request. If you request the interface for login, the server will add the set-cookie field in the response header after verification. Then, the browser will automatically attach the Cookie to the HTTP request header field in the next request, so that the server can know that the user has logged in. With that in mind, let’s look at the scenario:

  1. You go to empty your shopping cart, so you open the website www.maimaimai.com, and log in. You see that your shopping cart is so small that you have to buy more.

  2. You’re looking for something to buy when your best gay friend sends you a link to www.nidongde.com and says with a smile, “You know what I mean.” You open it without hesitation.

  3. You delightfully browse www.nidongde.com, but the site does something indescribable on the sly! Without the same origin policy restrictions, it made a request to www.maimaimai.com! Smart you must think of the above words “the server will be authenticated after the response header into the set-cookie field, and then the next time to send a request, the browser will automatically attach the Cookie in the HTTP request header field Cookie”, so that the illegal website is equivalent to logging in your account, can do whatever you want! If it’s not a Maimaimai account, but your bank account, then…

This is the legendary CSRF attack.

Looking at this CSRF attack, I was thinking that even with the same-origin policy restriction, cookies are in clear text, so they can still be removed. So I read some cookie-related articles: The server can set httpOnly, so that the front-end can not operate cookies. If there is no such setting, like XSS attacks can get cookies (Web security testing XSS); If secure is set to secure, HTTPS encrypted communication is transmitted to prevent interception.

Dom queries without same-origin policy restrictions
  1. One day you just wake up and receive an email saying that your bank account is at risk. Please click www.yinghang.com to change your password. You frighten urine, hurriedly point go in, or familiar bank login interface, you decisive input your account password, login go in to see money have little.

  2. You can’t see clearly, you usually visit the bank website is www.yinhang.com, but now you visit www.yinghang.com, what does this phishing website do?

// HTML<iframe name="yinhang" src="www.yinhang.com"Domconst iframe = window.frames[Domconst iframe = window.frames['yinhang']const node = iframe.document.getElementById('Input for your account password'Console. log(' Got this${node}, I still can't get the account password you just input?Copy the code
    Copy the code

From this, we know that the same origin policy can avoid some risks, not to say that the same origin policy is safe, but that the same origin policy is a basic security mechanism of the browser, after all, can increase the cost of attack. In fact, there is no penetrable shield, but the cost of the attack is not proportional to the benefits obtained after the successful attack.

Correct way to open across domains

The same origin policy is a good thing for browsers to do. It’s a defense against evil attacks, but you can’t keep everyone out just to keep the bad guys out. Yeah, nice guys like us should be able to cross borders if we open it the right way.

Here’s how to do it one by one, but before we do that, we need to do some preparatory work. To demonstrate cross-domain locally, we need:

  1. Run a random piece of front-end code (the following front-end is a random vUE) at http://localhost:9099.

  2. Run a copy of the backend (node Koa2) at http://localhost:9971.

The correct opening mode of an interface request is restricted by the same Origin policy

1.JSONP

In HTML tags, some tags, such as script and IMG, have no cross-domain restrictions, so we can take advantage of this.

Write a small interface on the back end:

Const {successBody} = require('.. /utli')class CrossDomain {static async jsonp (CTX) {const query = ctx.request.query ctx.cookies.set('tokenId'.'1') // query.cb is the name of the method used by the back end to return a directly executed method to the front end. ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}.'success'))})`  }}module.exports = CrossDomainCopy the code

Simple version front end:

<! DOCTYPE html><html> <head> <meta charset="utf-8">  </head>  <body>    <script type='text/javascript'> // The back end returns the directly executed method, which is equivalent to executing the method. Since the back end puts the returned data in the parameter of the method, it can get the RES. window.jsonpCb =function (res) {        console.log(res)      }    </script>    <script src='http://localhost:9871/api/jsonp? msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>  </body></html>Copy the code

Briefly encapsulate the front end:

/** * JSONP request tool * @param URL request address * @param data request parameter * @returns {Promise<any>} */const request = ({url, data}) => {returnnew Promise((resolve, Reject) => {const handleData = (data) => {const keys = object.keys (data) const keysLen = keys.lengthreturnkeys.reduce((pre, cur, index) => { const value = data[cur] const flag = index ! == keysLen - 1 ?'&' : ' '        return `${pre}${cur}=${value}${flag}`},' 'Const script = document.createElement() const script = document.createElement('script') / / interface to return data acquisition window. JsonpCb = (res) = > {document. Body. RemoveChild (script) delete window. JsonpCb resolve (res)} script.src = `${url}?${handleData(data)}& cb = jsonpCb ` document. The body. The appendChild (script)})} / / use the request ({url:'http://localhost:9871/api/jsonp', data: {// call MSG:'helloJsonp'  }}).then(res => {  console.log(res)})Copy the code

2. Empty iframe and add form

If you’re careful, you might notice that JSONP can only make GET requests, because essentially a script load resource is a GET, so what if you want to make POST requests?

Write a small interface on the back end:

Const {successBody} = require('.. /utli')class CrossDomain {  static async iframePost (ctx) {    let postData = ctx.request.body    console.log(postData)    ctx.body = successBody({postData: postData}, 'success')  }}module.exports = CrossDomainCopy the code

Front end:

Const requestPost = ({url, data}) => {const iframe = document.createElement('iframe')  iframe.name = 'iframePost'  iframe.style.display = 'none'  document.body.appendChild(iframe)  const form = document.createElement('form')  const node = document.createElement('input'// Register the load event handler for iframe if you need to do something when the response returns.'load'.function () {    console.log('post success'}) form.action = url // Execute form form.target = iframe.name form.method = in the specified iframe'post'  for (let name inData) {node.name = name node.value = data[name].toString() form.appendChild(node.clonenode ())} // The form element needs to be added to the main document. form.style.display ='none'Document. The body. The appendChild (form) form. Submit () / / after the form is submitted, you can delete the form, does not affect the next data. The document. The body. The removeChild (form)} / / RequestPost ({url:'http://localhost:9871/api/iframePost',  data: {    msg: 'helloIframePost'  }})Copy the code

3.CORS

CORS is a W3C standard, full name is “cross-origin Resource Sharing” (CROSS-domain resource sharing) CORS details. As the name suggests, this is standard practice for dealing with cross-domain problems. CORS has two types of requests, simple and non-simple.

Here is a reference to the above link ruan Yifeng teacher’s article to explain the simple request and non-simple request.

Browsers classify CORS requests into two categories: Simple request and not-so-simple Request.

As long as the following two conditions are met, it is a simple request.

(1) Request method is one of the following three methods:

  • 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

1. Simple requests

The backend:

Const {successBody} = require('.. /utli')class CrossDomain {static async cors (CTX) {const query = ctx.request.query // * When cookie does not carry ctx.set('Access-Control-Allow-Origin'.The '*')    ctx.cookies.set('tokenId'.'2')    ctx.body = successBody({msg: query.msg}, 'success')  }}module.exports = CrossDomainCopy the code

The front end doesn’t have to do anything, just make normal requests, and if cookies are required, both the front and back ends need to be set, as you’ll see in the non-simple request example below.

fetch(`http://localhost:9871/api/cors? msg=helloCors`).then(res => { console.log(res)})Copy the code

2, non-simple request non-simple request will send a pre-detection request, the return code is 204, pre-detection will be passed before the actual request, which will return 200. A non-simple request is triggered by adding an extra HEADERS to the request.

The backend:

Const {successBody} = require('.. /utli')class CrossDomain {static async cors (CTX) {const query = ctx.request.query The credentials need to be set on both the front and back ends, and the specified Origin ctx.set('Access-Control-Allow-Origin'.'http://localhost:9099')    ctx.set('Access-Control-Allow-Credentials'.true) // CORS requests that are not simple requests will be preceded by an HTTP query request, called"Preview"Set access-Control-request-method = access-Control-request-headers ctx.set()'Access-Control-Request-Method'.'PUT,POST,GET,DELETE,OPTIONS')    ctx.set('Access-Control-Allow-Headers'.'Origin, X-Requested-With, Content-Type, Accept, t')    ctx.cookies.set('tokenId'.'2')    ctx.body = successBody({msg: query.msg}, 'success')  }}module.exports = CrossDomainCopy the code

With so much code for one interface, what’s a more elegant way to treat all interfaces the same? See KOA2-CORS below.

const path = require('path')const Koa = require('koa')const koaStatic = require('koa-static')const bodyParser = require('koa-bodyparser')const router = require('./router')const cors = require('koa2-cors')const app = new Koa()const port = 9871app.use(bodyParser()) path.resolve(__dirname,'.. /dist'Corsapp. Use (cors({origin:function (ctx) {    return 'http://localhost:9099'  },  credentials: true,  allowMethods: ['GET'.'POST'.'DELETE'],  allowHeaders: ['t'.'Content-Type']}))// Route app.use(router.routes()).use(router.allowedmethods ())// Listen to port app.listen(9871)console.log(' [demo] start-quick is starting at port${port}`)Copy the code

Front end:

fetch(`http://localhost:9871/api/cors? MSG =helloCors', {// The cookie credentials are needed:'include'// Add additional headers to trigger non-simple headers requests: {'t': 'extra headers'  }}).then(res => {  console.log(res)})Copy the code

4. The agent

Imagine if we still used a front-end domain name when requesting a domain name, and then had something to forward the request to a real back-end domain name for us. Wouldn’t that be cross-domain? This is where Nginx comes in.

Nginx configuration:

server{    Listen 9099; Localhost server_name localhost; Localhost :9099/ API: http://localhost:9871 location ^~ / API {proxy_pass http://localhost:9871; }}Copy the code

The front end has nothing to do, and the back end has nothing to do except write interfaces.

Localhost :9099/ API: localhost:9099/ API: localhost:9099/ API: localhost:9099/ API All forwarded to the real server address http://localhost:9871 fetch('http://localhost:9099/api/iframePost', {  method: 'POST',  headers: {    'Accept': 'application/json'.'Content-Type': 'application/json'  },  body: JSON.stringify({    msg: 'helloIframePost'  })})Copy the code

The way Nginx forwards seems convenient! If the back-end interface is a public API, such as some public service to get the weather, the front end of the call should not be used to configure Nginx, if compatibility is ok (IE 10 or above), CROS is more general practice.

The correct way to open A Dom query under the same origin policy

1.postMessage

Window.postmessage () is an HTML5 interface that focuses on cross-domain communication between different Windows and different pages.

For demonstration purposes, let’s change hosts to 127.0.0.1 crossDomain.com. Now visiting the domain name crossDomain.com is the same as accessing 127.0.0.1.

This is http://localhost:9099/#/crossDomain, the sender:

<template>  <div>    <button @click="postMessage"> send the message to http://crossDomain.com:9099 < / button > < iframe name ="crossDomainIframe" src="http://crossdomain.com:9099"></iframe>  </div></template><script>export default {  mounted () {    window.addEventListener('message', (e) => {// The source must be verified hereif (e.origin === 'http://crossdomain.com:9099') {/ / results from the http://crossdomain.com:9099 reply the console log (e.d ata)}})}, the methods: {/ / send a message to http://crossdomain.com:9099postMessage () {      const iframe = window.frames['crossDomainIframe']      iframe.postMessage('I am [http://localhost:9099], please check whether you have a Dom with the ID of APP.'.'http://crossdomain.com:9099')    }  }}</script>Copy the code

Here is the http://crossdomain.com:9099, receives the message:

http://crossdomain.com:9099 < template > < div > I < / div > < / template > < script >export default {  mounted () {    window.addEventListener('message', (e) => {// The source must be verified hereif (e.origin === 'http://localhost:9099'Console. log(e.ata) // e.ource can be the object of a reply, A reference to http://localhost:9099 window object // e.rigin can be used as a targetOrigin E.s ource. PostMessage (` I am [http://crossdomain.com:9099], I know brother, this is what you want to know the results:${document.getElementById('app') ? 'there is a Dom with id app' : 'there is no Dom with ID app '}`, e.origin);      }    })  }}</script>Copy the code

The result can be seen:

2.document.domain

This mode applies only to iframes with the same primary domain name but different subdomain names.

Such as the main domain name is http://crossdomain.com:9099, domain name is http://child.crossdomain.com:9099, In this case, the two pages can access their respective window objects by specifying document.domain = crossDomain.com.

3. Cross-domain problem of canvas operating pictures

This should be a relatively unpopular cross-domain problem, Zhang Dashen has written that I will not teach you how to play ax: to solve canvas picture getImageData,toDataURL cross-domain problem

The last

Hopefully, after reading this article, when someone asks you a cross-domain question, you can turn up your mouth slightly, sneer, “Don’t ask me any more cross-domain questions,” and walk away.