1. 什么是跨域?
要了解什么是跨域,首先需要知道什么是同源策略。同源策略是由Netscape公司提出的一个注明的安全策略,所有支持JavaScript的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当页面执行一个脚本时会检查访问的资源是否同源,如果非同源,那么在请求数据时,浏览器就会在控制台中抱一个异常,提示拒绝访问。
同源策略一般又分为两种:
DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的;
XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起HTTP请求。
所以跨域是什么?
跨域,是指在请求一个不属于同源的资源时,这种行为就称为跨域,这是浏览器施加的安全限制。
2. 如何解决跨域请求
我们现在搭建两个web服务,一个监听本地8888端口,一个监听本地8887端口,8888端口返回一个html文件,该html文件中访问了8887端口:
server.js:
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response){
console.log('request url: ', request.url)
const html = fs.readFileSync('index.html', 'utf-8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8888)
console.log('server running on 8888')
server2.js:
const http = require('http')
http.createServer(function (request,response){
console.log('request url: ', request.url)
response.end('123')
}).listen(8887)
console.log('server running on 8887')
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>content</div>
</body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8887/')
xhr.send()
</script>
</html>
使用node server.js
和node server2.js
开启Web服务,访问8888端口:
我们发现控制台报错:
这里的异常信息是说,访问8887返回的数据被跨域策略阻止了,因为响应头中没有设置Access-Control-Allow-Origin
。
我们现在修改server2.js
,向返回数据的响应头添加相应内容:
const http = require('http')
http.createServer(function (request,response){
console.log('request url: ', request.url)
// 添加以下内容
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
})
response.end('123')
}).listen(8887)
console.log('server running on 8887')
重新运行node server2.js,然后刷新127.0.0.1:8888
,我们发现控制台没有报错信息了,并且我们从8887拿到了正确的返回信息:
现在,我们访问8887端口返回的数据响应头中多出来了'Access-Control-Allow-Origin': '*'
,正是因为有了这一个说明,才能使浏览器接收8887返回的数据。
3. 网络请求从发起到返回
如果我们没有设置'Access-Control-Allow-Origin': '*'
,浏览器会报错,我们拿不到8887端口返回的内容,但是,这是否说明是8887端口拒绝了我们的访问呢?
其实,即使我们不做任何设置,只要向8887发起请求,8887就会返回其应该返回的内容(这里8887需要返回‘123’),同时,浏览器也能获取到这个返回数据,当浏览器收到这个返回数据时,浏览器会检查这个数据和当前页面是否同源,如果不符合同源策略,浏览器就会舍弃这个数据。
当我们为返回数据设置了'Access-Control-Allow-Origin': '*'
之后,浏览器在对资源进行同源判定时,就会根据这个信息来判断资源是否和当前页面同源。
如果我们修改server2.js:
const http = require('http')
http.createServer(function (request,response){
console.log('request url: ', request.url)
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8886'
})
response.end('123')
}).listen(8887)
console.log('server running on 8887')
然后访问127.0.0.1:8888,这时候我们就会发现,返回数据的响应头是'Access-Control-Allow-Origin': '127.0.0.1:8886'
,当浏览器在进行同源判定时,127.0.0.1:8888和127.0.0.1:8886端口号不同,所以不是同源,会报错:
使用标签解决跨域
当网站开始执行脚本,会检测这个脚本的来源,如果这个脚本的来源与当前网站不同源的话,就会限制其执行,即受到同源策略的限制。有没有一种方式使其他来源的脚本转化成和当前页面同源的呢?
使用标签来加载非同源资源,当加载完成之后再注入到页面中,浏览器就会将其认为是同源的:
1 <img src=xxx>
2 <link href=xxx>
3 <script src=xxx>
我们修改index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>content</div>
</body>
<script src="http://127.0.0.1:8887"></script>
</html>
再次访问127.0.0.1:8888,查看8887端口返回的数据:
我们发现,即使8887返回数据的响应头中说明Access-Control-Allow-Origin: 127.0.0.1:8886
,但是我们依旧可以拿到这个数据,并且控制台没有报错信息。
其实,这就是JSONP的实现机制,JSONP跨域是通过动态创建script标签,然后通过其src属性进行跨域请求的。
多个服务器请求同一个跨域资源
试想,我们现在增加一个server3
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response){
console.log('request url: ', request.url)
const html = fs.readFileSync('index.html', 'utf-8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8886)
console.log('server running on 8886')
server3.js和server.js唯一的不同就是监听的端口不一样,server3监听8886端口,现在我们面临一个问题就是:127.0.0.1:8888和127.0.0.1:8886都需要向127.0.0.1:8887发送请求,如果我们还像之前那样,'Access-Control-Allow-Origin': 'http://127.0.0.1:8888'
,始终只能满足其中一个,如果我们使用正则或是通配符,有可能出现安全隐患。
这时候,我们可以设置一个共享资源地址的数组,然后如果来源地址在这个数组中,那么就将
Access-Control-Allow-Origin设置成对应的地址:
修改server2.js:
const http = require('http')
http.createServer(function (request,response){
console.log('request url: ', request.url)
const hosts = ['http://127.0.0.1:8888', 'http://127.0.0.1:8886']
const shareUrl = ['http://127.0.0.1:8888/', 'http://127.0.0.1:8886/']
let url = shareUrl.findIndex(item => item === request.headers.referer)
response.writeHead(200, {
'Access-Control-Allow-Origin': url > -1 ? hosts[url] : hosts[0]
})
response.end('123')
}).listen(8887)
console.log('server running on 8887')