大前端百科全书,前端界的百科全书,记录前端各相关领域知识点,方便后续查阅及面试准备
关键词
关键词:同源策略、CORS、反向代理
- 什么是同源策略?浏览器的同源策略
- 解决跨域都有哪些手段?
- 什么是跨域?都有哪些方式会造成跨域?
- 说一下你理解的CORS
一、什么是同源策略?浏览器的同源策略
同源策略是浏览器的安全策略,用于限制一个origin的文档或者加载的脚本如何能与另一个源的资源进行交互。
它能帮助阻隔恶意文档,减少可能被攻击的媒介。
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。
当一个浏览器的两个tab页中分别打开谷歌和百度的页面,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。
如果是非同源,那么在请求数据的时候,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略是浏览器的行为,是为了保护本地数据不被JavaScript代码获取回来的数据污染,因此拦截的是客户端发出的请求回来的数据接收,即请求发送了,服务器响应了,但是无法被浏览器接收。
一个域下的 js 脚本在未经允许的情况下,不能够访问另一个域的内容。这里的同源的指的是两个域的协议、域名、端口号必须相同,否则则不属于同一个域。同源政策主要限制了三个方面:
- 第一个是当前域下的 js 脚本不能够访问其他域下的 cookie、localStorage 和 indexdDB。
- 第二个是当前域下的 js 脚本不能够操作访问操作其他域下的 DOM
- 第三个是当前域下 ajax 无法发送跨域请求
同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。
二、解决跨域都有哪些手段
jsonp
- 动态创建script标签,拼接好参数
- 在url上指定一个回调函数
- 只能用于get请求
使用 jsonp 来实现跨域请求,它的主要原理是通过动态构建 script 标签来实现跨域请求,因为浏览器对 script 标签的引入没有跨域的访问限制 。通过在请求的 url 后指定一个回调函数·,然后服务器在返回数据的时候,构建一个 json 数据的包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get请求。
link、script、img、background:url()、@font-face()等均不受跨域限制
(function (w) {
/**
* jsonp的实现
* @param {Object}option
*/
function jsonp(option) {
// 0.产生不同的函数名 - 解决了调用多次请求,造成覆盖的问题
// 如果方法名相同,调用多次,都会执行最后一个,出现覆盖现象
let callBackName = 'lcy' + Math.random().toString().substr(2) + Math.random().toString().substr(2);
// 1.函数挂载在全局
w[callBackName] = function(data){
option.success(data);
// 删除script标签
document.body.removeChild(scriptEle);
};
// 2.处理url
option.url = option.url + '?' + getStrWithObject(option.data, 'callback=' + callBackName);
// 3.创建script标签插入body
let scriptEle = document.createElement('script');
scriptEle.src = option.url;
document.body.appendChild(scriptEle);
}
w.jsonp = jsonp;
})(window);
/**
* 把对象转换成拼接字符串
* @param paramObj 对象参数
* @returns {string} 字符串
*/
function getStrWithObject(paramObj, words){
let resArr = [];
// 1.转换对象
for(let key in paramObj){
let str = key + '=' + paramObj[key];
resArr.push(str);
}
resArr.push(words);
// 3.数组转换成字符串
return resArr.join("&");
}
CORS
使用 CORS 的方式,CORS 是一个 W3C 标准,全称是"跨域资源共享"。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,因此我们只需要在服务器端配置就行。
浏览器将CORS请求分成两类:
简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求。
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2)HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值
application/x-www-form-urlencoded、multipart/form-data、text/plain凡是不同时满足上面两个条件,就属于非简单请求。
浏览器将 CORS 请求分成两类:简单请求和非简单请求。
-
对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是会在头信息之中,增加一个 Origin 字段。Origin 字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,ajax 不会收到响应信息。如果成功的话会包含一些以 Access-Control- 开头的字段。
-
非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
nginx
- nginx.conf配置cors
location / {
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {
return 204;
}
}
- 反向代理
Nginx作为反向代理服务器,就是把http请求转发到另一个或者一些服务器上。通过把本地一个url前缀映射到要跨域访问的web服务器上,就可以实现跨域访问。对于浏览器来说,访问的就是同源服务器上的一个url。而Nginx通过检测url前缀,把http请求转发到后面真实的物理服务器。并通过rewrite命令把前缀再去掉。这样真实的服务器就可以正确处理请求,并且并不知道这个请求是来自代理服务器的。
server {
location / {
root html;
index index.html index.htm;
//允许cros跨域访问
add_header 'Access-Control-Allow-Origin' '*';
}
//自定义本地路径
location /apis {
rewrite ^.+apis/?(.*)$ /$1 break;
include uwsgi_params;
proxy_pass http://www.binghe.com;
}
}
node
http.createServer((req,res)=>{
//设置允许跨域的域名,*代表允许任意域名跨域
res.setHeader("Access-Control-Allow-Origin","*");
//跨域允许的header类型
res.setHeader("Access-Control-Allow-Headers","Content-type,Content-Length,Authorization,Accept,X-Requested-Width");
//跨域允许的请求方式
res.setHeader("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");
//设置响应头信息
res.setHeader("X-Powered-By",' 3.2.1')
//让options请求快速返回
if(req.method == "OPTIONS"){return res.end();}
}
}).listen(3000);
websocket
使用 websocket 协议,这个协议没有同源限制。
postMessage
使用 postMessage 来解决的方法,这是一个 h5 中新增的一个 api。
通过它我们可以实现多窗口间的信息传递,通过获取到指定窗口的引用,然后调用 postMessage 来发送信息,在窗口中我们通过对 message 信息的监听来接收信息,以此来实现不同源间的信息交换。如果是像解决 ajax 无法提交跨域请求的问题,我们可以使用 jsonp、cors、websocket 协议、服务器代理来解决问题。
父级html
<iframe
id="iframe"
src="http://10.73.154.73:8088/rbc/t/search_role.html"
style="display:none;"
></iframe>
<script>
var iframe = document.getElementById('iframe');
iframe.onload = function() {
var data = {
name: 'aym',
type:'wuhan'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://10.73.154.73:8088');
};
// 接受domain2返回数据,这边给延迟的原因,因为同步传输时,页面不一定立马拿到数据,所以给延迟
setTimeout(function(){
window.addEventListener('message', function(e) {
alert('data from domain2 sss ---> ' + e.data);
}, false);
},10)
</script>
iframe中接受,回传数据
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
console.log(e.data);
var data = JSON.parse(e.data);
if (data) {
data.number = 16;
data.age = 89;
data.icon = 'sfafdafdafasdf';
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://10.73.154.72:8088');
}
}, false);
</script>
document.domain
两个域名必须属于同一基础域名,并且所有的协议端口完全一致,否则无法跨域
将 document.domain 设置为主域名,来实现相同子域名的跨域操作,这个时候主域名下的 cookie 就能够被子域名所访问。
同时如果文档中含有主域名相同,子域名不同的 iframe 的话,我们也可以对这个 iframe 进行操作。
如果是想要解决不同跨域窗口间的通信问题,比如说一个页面想要和页面的中的不同源的iframe 进行通信的问题,我们可以使用 location.hash 或者 window.name 或者 postMessage 来解决问题
location.hash
使用 location.hash 的方法,我们可以在主页面动态的修改 iframe 窗口的 hash 值,然后在 iframe 窗口里实现监听函数来实现这样一个单向的通信。因为在 iframe 是没有办法访问到不同源的父级窗口的,所以我们不能直接修改父级窗口的 hash 值来实现通信,我们可以在 iframe 中再加入一个 iframe ,这个 iframe 的内容是和父级页面同源的,所以我们可以 window.parent.parent 来修改最顶级页面的 src,以此来实现双向通信。
window.name
使用 window.name 的方法,主要是基于同一个窗口中设置了 window.name 后不同源的页面也可以访问,所以不同源的子页面可以首先在 window.name 中写入数据,然后跳转到一个和父级同源的页面。这个时候级页面就可以访问同源的子页面中 window.name 中的数据了,这种方式的好处是可以传输的数据量大。
三、什么是跨域?都有哪些方式会造成跨域?
什么是跨域
跨域指的是非同源的资源之间尝试着进行交互通信,而由于受浏览器同源策略的限制,无法正常进行交互通信。
浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JS实施的安全限制。无法跨域是浏览器对于用户安全的考虑,同源策略限制了以下行为:Cookie、LocalStorage和IndexDB 无法读取DOM和JS对象无法获取,ajax请求发送不出去。
哪些方式会造成跨域
非同源请求、服务端设置cors限制
四、说一下你理解的CORS
CORS简介
CORS(Cross Origin Resource Sharing)即是跨来源资源共享,通俗的来说就是跨域请求。在以前跨域可以采用代理、JSONP等方式,而在Modern浏览器面前,这些终将成为过去式,因为有了CORS。
CORS在最初接触的时候只大概了解到,通过服务器端设置Access-Control-Allow-Origin响应头,即可使指定来源像访问同源接口一样访问跨域接口,最近在使用CORS的时候,由于需要传输自定义Header信息,发现原来CORS的规范远不止这些
CORS可以分为两种:简单请求、 复杂请求
1. 简单请求
HTTP方法:HEAD GET POST
HTTP头信息不超出以下几种字段:Accept Accept-Language Content-Language Last-Event-ID Content-Type[下列方式:application/x-www-form-urlencoded multipart/form-data text/plain]
任何一个不满足上述要求的请求,即被认为是复杂请求。一个复杂请求不仅有包含通信内容的请求,同时也包含预请求
简单请求的发送从代码上来看和普通的hxr没太大区别,但是HTTP头当中要求总是包含一个域的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,并不是开发者代码可以触及到的。
简单请求的部分响应头及解释如下:
- Access-Control-Allow-Origin(必需):如果没有的话按照请求失败处理,该项数据的可见范围,如果希望数据对任何人可见,填写
"*" - Access-Control-Allow-Credentials(可选):该项标志着请求当中是否包含cookies信息。只有一个可选值:true。如果不包含cookies请忽略该选项而不是填写false。这一项跟XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentials为true时该项也为true。
- Access-Control-Expose-Headers(可选):该项确定XmlHttpRequest2对象当中getResponseHeader()方法所能获取得的额外信息。通常情况下,getResponseHeader()方法只能获得如下的信息
Cache-Control Content-Language Content-Type Expires Last-Modified Pragma当你需要访问额外的信息时,就需要在这一项当中填写并用逗号进行分隔
如果仅仅是简单请求,那么即便不用CORS也没有什么大不了的,但是CORS的复杂请求就令CORS显得更加有用了。简单来说,任何不满足上述简单要求的请求,都属于复杂请求。
2. 复杂请求
复杂请求表面上看起来和简单请求使用上差不多,但实际上浏览器发送了不止一个请求。其中最先发送的是一种"预请求",此时作为服务端,也需要返回"预回应"作为响应。预请求实际上是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。
预请求以options形式发送,当中同样包含域,并且还包含了两项CORS特有的内容:
- Access-Control-Request-Method:该项内容是实际请求的种类,可以是get、post之类的简单请求,也可以是put、delete等等
- Access-Control-Request-Headers:该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部
显而易见,这个预请求实际上是在为后面的实际请求发送一个权限请求,在预回应返回的内容中,服务端应当对这两项进行回复,来让浏览器确定请求是否能够成功完成。
复杂请求的部分响应头以及解释:
- Access-Control-Allow-Origin(必需):和简单请求一样的,必须包含一个域
- Access-Control-Allow-Method(必需):这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然可以返回所有允许的方法,以便客户端将其缓存
- Access-Control-Allow-Headers(必需):前提是当预请求中包含Access-Control-Request-Headers时必须包含,这是对预请求当中Access-Control-Request-Headers的回复,一样是以逗号分隔的列表,可以返回所有支持的头部
- Access-Control-Allow-Credentials(可选):和简单请求中的作用相同
- Access-Control-Max-Age(可选):以秒为单位的缓存时间。预请求的发送在一定的时间内允许尽可能被缓下来
一旦预回应如期而至,多请求的权限也都已满足,则实际请求开始发送