Don't ask me cross domain questions anymore
After writing this article, I think that in the future, I should categorize all the common knowledge of this foundation into this "Don't ask me XX questions again" and form a series of content. I hope that after everyone reads it, if someone asks you these questions again, you will secretly feel happy and say, "Hey hey, it's time to showcase true technology
1、 Don't ask me any more questions about the direction of this
The word 'cross domain' is like a piece of dog skin ointment stuck to every front-end developer, and you will inevitably encounter this problem in your work or interview. In order to cope with the interview, I always memorize a few plans casually, and I don't know why I have to do this. After finishing the pros and cons, I can throw them away. I don't think I will use so many messy plans in my work. When it comes to real work, the development environment is handled by webpack dev server, and even the big shots on the server end will be ready. I don't care what is configured, as it won't cross domains anyway. The days have just passed, and one day, I feel like I can't continue to mess around like this anymore. I must thoroughly understand this thing! So there is this article.
To master cross domain, the first thing to know is why the problem of cross domain arises
Indeed, as brick workers like us, we are just trying to make a living. Just set up a good interface and tell me that we have crossed domains. This kind of thing that hinders us from easily moving bricks is really disgusting! Why cross domain? Who is causing the problem? To find the culprit of this problem, please click on the browser's homologous strategy.
It's really difficult to understand such an official thing, it's okay, at least you know, because the browser's homologous strategy leads to cross domain, which is the browser doing things.
So, why are browsers doing things? Just don't want to give us a good life? For such questioning, the browser flipped the pot and said, "The same source policy restricts how documents or scripts loaded from the same source interact with resources from another source. This is an important security mechanism used to isolate potential malicious files
This official script is really difficult to understand. It's okay, at least you know, it seems to be a security mechanism.
So, why on earth is such a security mechanism needed? What problem has this security mechanism solved? Don't worry, let's continue our research.
Two major dangerous scenarios without homologous policy restrictions
As far as I know, browsers implement this same origin strategy from two aspects: one is for interface requests, and the other is for Dom queries. Imagine the danger of these two actions without such restrictions.
Interface requests without homology policy restrictions
There is a small thing called a cookie that everyone should know, which is usually used to handle login and other scenarios, with the purpose of letting the server know who made this request. If you request an interface to log in, the server will add a Set Cookie field to the response header after verification. Then, the next time you send a request, the browser will automatically attach a cookie to the HTTP request header field Cookie, and the server will know that the user has already logged in. After learning this, let's take a look at the scene:
- You are about to clear your shopping cart, so you open the shopping website www.maimaimai.com and log in successfully. Upon seeing that there are so few items in the shopping cart, you still need to buy more.
- While you were looking for something to buy, your good friend sent you a link to www.nidongde.com and said with a smile, "You understand," and you didn't hesitate to open it.
- You are browsing www.nidongde.com with great interest, but to your surprise, this website has secretly done something indescribable! Due to the lack of restrictions from the same source policy, it initiated a request to www.maimaimai.com! Clever of you, you must have thought of the above sentence: "After the server validates, it will add a Set Cookie field to the response header. Then, the next time the request is sent, the browser will automatically attach the cookie to the HTTP request header field Cookie." In this way, this illegal website is equivalent to logging into your account and doing whatever you want! If this is not a buy buy account, but your bank account, then
This is the legendary CSRF attack. Talking about CSRF attack methods.
After watching this wave of CSRF attacks, I was thinking that even with the restriction of the same origin policy, cookies are in plain text and cannot still be taken down. So I read some articles related to cookies and talked about the mechanisms and security of cookies, cookies, and sessions. I learned that the server can set httpOnly so that the front-end cannot operate cookies. If there is no such setting, XSS attacks can obtain XSS for cookie web security testing; Set secure to ensure transmission in encrypted communication over HTTPS to prevent interception.
Dom queries without homologous policy restrictions
One day, you just woke up and received an email saying that your bank account was at risk. Quickly click on www.yinghang.com to change your password. You're scared to pee. Hurry up and click on the familiar bank login interface. Enter your account password and log in to see if the money is missing. - With dim sleep, you didn't see clearly. The bank website you usually visit is www.yinhang.com, but now you are visiting www.yinhang.com. What does this phishing website do?
//HTML
//JS
//Due to the lack of homologous policy restrictions, phishing websites can directly obtain Doms from other websites
Const iframe=window. frames ['yinhang ']
Const node=iframe. document. getElementById ('Input for the account password you entered ')
Console.log (I have obtained this ${node}, but can't I still obtain the account password you just entered?)
From this, we know that the same origin policy can indeed avoid some dangers. It does not mean that having the same origin policy is safe, but rather that the same origin policy is the most basic security mechanism for browsers, after all, it can increase the cost of attacks a bit. Actually, there is no shield that cannot be pierced, but the cost of an attack is not directly proportional to the benefits obtained after a successful attack.
Correct opening method across domains
After understanding the same origin strategy, we should eliminate misunderstandings about browsers. The same origin strategy is a good thing that browsers do and is used to defend against attacks from evil sources. However, we should not turn everyone out in order to prevent bad people from entering. That's right, as long as we open it correctly, we gentlemen should be able to cross domains.
Below, we will demonstrate the correct opening method one by one, but before that, there are some preparatory work to be done. In order to demonstrate cross domain locally, we need to:
- Run a copy of the front-end code randomly (the following front-end is a Vue that runs randomly), with the address http://localhost:9099 .
- Run a copy of the backend code randomly (the following backend is the node koa2 that runs randomly), and the address is http://localhost:9971 .
The Correct Opening Method of Interface Requests under the Restriction of Same Origin Policy - JSONP
In HTML tags, some tags such as script and img for accessing resources do not have cross domain restrictions. Taking advantage of this, we can do the following:
Write a small interface on the backend
//Tool for handling successful and failed return formats
const {successBody} = require('../utli')
class CrossDomain {
static async jsonp (ctx) {
//The parameters passed from the front-end
const query = ctx.request.query
// Set a cookie
ctx.cookies.set('tokenId', '1')
// Query.cb is a method name agreed upon between the front and back ends, which actually means that the backend returns a directly executed method to the front-end. Since the front-end initiates a request using a script tag, returning this method is equivalent to executing immediately, and the data to be returned is placed in the parameters of the method.
ctx.body =${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})
}
}
module.exports = CrossDomain
Simple version front-end
<!DOCTYPE html>
<meta charset="utf-8">
<script type='text/javascript'>
// The backend returns a method for direct execution, which is equivalent to executing this method. Since the backend places the returned data in the parameters of the method, res can be obtained here.
window.jsonpCb = function (res) {
console.log(res)
}
</script>
<script src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb' type='text/javascript'></script>
Simply encapsulate the front-end routine
/**
*JSONP Request Tool
*Address requested by @ param URL
*Parameters for @ param data request
*@ returns {Promise
*/
const request = ({url, data}) => {
return new Promise((resolve, reject) => {
//Process parameter transfer in the form of xx=yy&aa=bb
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}`
}, '')
}
//Dynamically creating script tags
Const script=document. createElement ('script ')
//Obtaining data returned by the interface
window.jsonpCb = (res) => {
document.body.removeChild(script)
delete window.jsonpCb
resolve(res)
}
script.src = `${url}?${handleData(data)}&cb=jsonpCb`
document.body.appendChild(script)
})
}
// usage
request({
url: 'http://localhost:9871/api/jsonp',
data: {
// Chuanshen
msg: 'helloJsonp'
}
}).then(res => {
console.log(res)
})
2.Empty iframe with form
Careful friends may find that JSONP can only send GET requests, because loading resources with scripts is essentially a GET. So what if a POST request needs to be sent?
Write a small interface on the backend
//Tool for handling successful and failed return formats
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 = CrossDomain
front end
Const requestPost=({url, data})=>{
//First, create an iframe for sending 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 perform some actions on response returns
Iframe. addEventListener ('load ', function()){
Console. log ('post success')
})
Form. action=URL
//Execute the form in the specified iframe
Form. target=iframe. name
Form. method='post'
For (let name in data){
Node. name=name
Node. value=data [name]. toString()
Form. appendChild (node. loneNode())
}
//Form elements need to be added to the main document
Form. style. display='none'
Document. body. appendChild (form)
Form. submit()
//After the form is submitted, it can be deleted without affecting the next data transmission
Document. body. removeChild (form)
}
//Usage
requestPost({
url: 'http://localhost:9871/api/iframePost',
data: {
msg: 'helloIframePost'
}
})
- CORS
CORS is a W3C standard, also known as Cross origin resource sharing. You can tell by name that this is the standard practice for dealing with cross domain issues. CORS has two types of requests, simple and non simple.
Here is a reference to the article by Teacher Ruan Yifeng from the link above to explain simple and non simple requests.
The browser divides CORS requests into two categories: simple requests and not so simple requests.
As long as both of the following conditions are met, it is considered a simple request.
(1) The request method is one of the following three methods:
HEAD
GET
POST
(2) The header information of HTTP does not exceed the following fields:
Accept
Accept Language
Content Language
Last Event ID
Content Type: limited to three values: application/x-www form urlencoded, multipart/form data, text/plain - Simple request
back-end
//Tool for handling successful and failed return formats
const {successBody} = require('../utli')
class CrossDomain {
static async cors (ctx) {
const query = ctx.request.query
// *Cookies will not be included in HTTP requests
ctx.set('Access-Control-Allow-Origin', '*')
ctx.cookies.set('tokenId', '2')
ctx.body = successBody({msg: query.msg}, 'success')
}
}
module.exports = CrossDomain
The front-end doesn't need to do anything, just send requests normally. If cookies are needed, both the front-end and back-end should be set up, as shown in the non simple request example below.
fetch(http://localhost:9871/api/cors?msg=helloCors
).then(res => {
console.log(res)
})
- Non simple request
A non simple request will issue a pre detection request with a return code of 204. Only after the pre detection is passed will the request be truly issued, which returns 200. Here, an additional header is added when the front-end sends a request to trigger non simple requests.
Clipboard.png
back-end
//Tool for handling successful and failed return formats
Const {successBody}=require ('../utli')
Class CrossDomain{
Static asynchronous cors (ctx){
Const query=ctx. request. query
//If cookies need to be included in HTTP requests, credentials need to be set on both the front and back ends, and the specified origin needs to be set on the backend
Ctx. set ('Access Control Allow Origin ',' http://localhost:9099 ')
Ctx. set ('Access Control Allow Credentials', true)
//CORS requests that are not simple requests will have an HTTP query request added before formal communication, called a "preflight" request
//In this case, in addition to setting the origin, it is also necessary to set the Access Control Request Method and 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=CrossDomain
There is so much code to write for an interface, what more elegant way is there to handle all interfaces uniformly? 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=9871
App. use (bodyParser())
//Processing static resources here is the directory after the front-end build is completed
App. use (koaStatic(
Path. resolve (__dirname, '../dist')
))
//Process Cors
App. use (cors){
Origin: function (ctx){
Return ' http://localhost:9099 '
},
Credentials: true,
AllowMethods: ['GET ',' POST ',' DELETE '],
AllowHeaders: ['t ',' Content Type ']
})
//Routing
App. use (router. routes()). use (router. allowedMethods())
//Listening port
App. listen (9871)
Console. log ([demo] start quick is starting at port ${port}
)
front end
Fetch (http://localhost:9871/api/cors?msg=helloCors
{
//Need to bring a cookie
Credentials: 'include',
//Add additional headers here to trigger non simple requests
Headers:{
't ':' extra headers'
}
}Then (res=>{
Console. log (res)
}) Agency
Think about it, if we still use the front-end domain name when requesting, and there is something to help us forward this request to the real back-end domain name, wouldn't it avoid cross domain? At this point, Nginx appeared.
Nginx configuration
Server{Listening to port 9099
Listen 9099;
The domain name is localhost
Server_ Name localhost;
Anything like localhost: 9099/API will be forwarded to the true server address http://localhost:9871
Location ^~/apis{
Proxy_ Pass http://localhost:9871 ;
}
}
There's no need for the front-end to do anything, except write interfaces, and there's no need for the back-end to do anything
//Directly use the domain name on the front-end side when requesting http://localhost:9099 This will not cross domains, and then Nginx listens to everything like localhost: 9099/apis and forwards it to the true 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'
})
})
The Nginx forwarding method seems very convenient! But this usage also depends on the scenario. If the backend interface is a public API, such as some public services that obtain weather information, the front-end call should not have the operation and maintenance personnel configure Nginx. If compatibility is not a problem (IE 10 or above), CROS is the more common approach.
The Correct Opening Method of Dom Queries under the Restriction of Same Origin Policy- PostMessage
Window. postMessage () is an interface in HTML5 that focuses on cross domain communication between different windows and pages.
For the convenience of the demonstration, we will change the hosts to 127.0.0.1 crossDomain.com. Now accessing the domain name crossDomain.com is equivalent to accessing 127.0.0.1.
This is http://localhost:9099/#/crossDomain From
This is http://crossdomain.com:9099 Received by
The results can be seen as follows:
Clipboard.png
- document.domain
This method is only suitable for cross domain iframes with the same primary domain but different sub domains.
For example, the main domain name is http://crossdomain.com:9099 The sub domain name is http://child.crossdomain.com:9099 In this case, specifying document. domain for both pages, i.e. document. domain=crossdomain. com, allows access to their respective window objects. - Cross domain problem of canvas operation images
This should be a relatively unpopular cross domain issue. Zhang Dashen has already written about it, so I will no longer try to solve the canvas image getImageData, toDataURL cross domain problem
last
I hope that after reading this article, if someone asks me cross domain questions again, you can raise your mouth slightly and sneer, "Don't ask me cross domain questions anymore
Soaring away.