After writing this article, I thought, maybe we could put all this basic common knowledge into “Stop asking me questions” and form a series of content, hoping that after reading this, someone will ask you these questions and you will feel happy: “Hey hey, it’s time to show the real technology!” 1. Stop asking me about the orientation of this

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 your browser’s same-origin policy. 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. After knowing this, let’s look at the scenario: 1. You are going to empty your shopping cart, so you open maimaimai.com www.maimaimai.com, and log in successfully. 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 talk about CSRF attack mode. 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 and talked about cookie, cookie /Session mechanism and security. I learned that the server can set httpOnly so that the front-end can not operate cookies. If there is no such setting, Like XSS attacks to get the XSS of cookieWeb security tests; 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 wake up and receive an email telling you 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 don’t see clearly, you usually visit the bank website www.yinhang.com, but now you visit www.yinghang.com, what does this phishing website do?

HTML <iframe name="yinhang" SRC ="www.yinhang.com"></iframe> Phishing site can be directly to other sites Dom const iframe = window. The frames [' yinhang] const node = iframe. Document. GetElementById (' you enter the password Input) Console. log(' got this ${node}, can'T I get the password you just entered? ')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 to: 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

JSONP in HTML tags, some tags such as script, img, such as access to resources are not cross-domain restrictions, to take advantage of this, we can do:

Write a small interface on the back end

Const {successBody} = require('.. /utli') class CrossDomain {static async jsonp (CTX) {const query = ctx.request.query Ctx.cookie. set('tokenId', '1') // query.cb is the method name of the convention, which means that the back end returns a directly executed method to the front end. And put the data to be returned in the method parameters. ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})` } } module.exports = CrossDomainCopy the code

Simple front end

<! DOCTYPE HTML > < HTML > <head> <meta charset=" utF-8 "> </head> <body> <script type='text/javascript'> This is equivalent to executing this method, and since the back end puts the returned data in the parameter of the method, it gets the RES here. 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}) => { return new Promise((resolve, Reject) => {const handleData = (data) => {const keys = object.keys (data) const keysLen = keys.length return keys.reduce((pre, cur, index) => { const value = data[cur] const flag = index ! == keysLen - 1 ? '&' : '' return `${pre}${cur}=${value}${flag}` }, } const script = document.createElement('script') // Get window.jsonpcb = (res) => { document.body.removeChild(script) delete window.jsonpCb resolve(res) } script.src = `${url}? ${the handleData (data)} & cb = jsonpCb ` document. The body. The appendChild (script)})} / / use the request ({url: 'http://localhost:9871/api/jsonp' data: {/ / preach and MSG: 'helloJsonp'}}). Then (res = > {the console. The log (res)})Copy the code

JSONP can only send a GET request, because the script load resource is essentially a GET request, so what if I send a POST request?

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

The front end

const requestPost = ({url, 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. Function () {console.log('post success')}) form.action = url // Execute form form.target = iframe.name in the specified iframe form.method = 'post' for (let name in data) { node.name = name node.value = data[name].toString() Form. The appendChild (node. The cloneNode ())} / / form elements need to be added to the main document. The 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. RemoveChild (form)} / / use 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 request backend

Const {successBody} = require('.. /utli') class CrossDomain {static async cors (CTX) {const query = ctx.request.query // * When cookies are not carried in HTTP requests ctx.set('Access-Control-Allow-Origin', '*') 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 requests

A non-simple request will issue a precheck request with a return code of 204. The request will not actually be issued until the precheck passes, and 200 will be returned. A non-simple request is triggered by adding an extra HEADERS to the request.

The back-end

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. Set (' access-Control-allow-origin ', 'http://localhost:9099') ctx.set(' access-Control-allow-credentials ', true) // For CORS requests that are not simple, an HTTP query is added before formal communication. Called a "preflight" request (preflight) // In this case, instead of setting origin, Set access-Control-request-method (' access-Control-request-method ') 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() App.use (koaStatic(path.resolve(__dirname, '.. Use (cors({origin: function (CTX) {return 'http://localhost:9099'}, credentials: true, allowMethods: ['GET', 'POST', 'DELETE'], allowHeaders: ['t', 'content-type ']}) // app.use(router.routes()).use(router.allowedmethods ()) // app.listen(9871) console.log(`[demo] start-quick is starting at port ${port}`)Copy the code

The front end

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

4. Proxy: If we still use the front-end domain name when requesting a domain name, and then something forwards the request to the real back-end domain name for us, wouldn’t cross domains be avoided? 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

There’s nothing to do on the front end, nothing to do on the back end except write interfaces, right

Localhost :9099/ API: localhost:9099/ API: localhost:9099/ API: localhost:9099/ API Are forwarded to the real server address http://localhost:9871 the 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 implementing 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

< the template > < div > < button @ click = "postMessage" > send a 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) = > {/ / must be done to source check if here (e.o rigin = = = 'http://crossdomain.com:9099') {/ / reply results from the http://crossdomain.com:9099 console.log(e.data) } }) }, methods: {/ / send message to http://crossdomain.com:9099 postMessage () {const iframe window. = frames [' crossDomainIframe] Iframe. PostMessage (' I am [http://localhost:9099], please check your side have any id for the app Dom ', 'http://crossdomain.com:9099')}}} < / script >Copy the code

This is crossDomain.com :9099, the receiver

http://crossdomain.com:9099 < template > < div > I < / div > < / template > < script > export default {mounted () { window.addEventListener('message', If (e.orein === 'http://localhost:9099') {console.log(e.ata) // if (e.orein === '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')? ':' no Dom'} ', e.olin); } }) } } </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 crossdomain.com: 9099, subdomain is 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 rare cross-domain problem

The last

Hopefully, after reading this article, when someone asks cross-domain questions, you can turn your mouth slightly upward and sneer: “Don’t ask me cross-domain questions again.” Walk away.

Classic front-end technology books


comments