同源策略
首先明确一点,同源策略是浏览器的策略.其目的是防止非同源页面胡搞,非同源资源互取.
1.谁和谁同源
当前页面的url和iframe嵌套的页面url
当前页面的url和向服务器发送的url,主要是针对XMLHttpRquest发送的请求
2.怎样算是同源
https://i.cnblogs.com:443/posts/edit https ------->协议 i.cnblogs.com ------->域名 443 ---------->端口号 /posts/edit ---------->路径
当协议、域名、端口号都相同时为同源,其他情况是非同源.
跨域
1.什么是跨域
当想不满足同源策略的url发送请求时就会发生跨域,所以理论上跨域是个前端概念.
2.跨域造成的影响
(1)发送非同源请求时会被浏览器限制
(2)iframe嵌套页面的联动受限
如何解决跨域问题
1.JSONP
JSONP跨域的先决条件有下面几条:
(1)<script>、<link>、<img>这些标签也可以请求外部资源,并且请求不受同源策略限制,比如script的src属性值是一个http路径时就会发出相关的请求
(2)系统会把<script>内部的内容按照js代码来解析执行
(3)服务器配合
满足以上条件后,我们再一步步分析JSONP的原理
首先,我们先发送一个JSONP请求.
let myScript = document.createElement('script') let bodyDom =document.getElementsByTagName('body')[0] myScript.src='https://g.wuen.gun:8000/test.js'//瞎写的域名 bodyDom.appendChild(myScript)
类似这样的方法,我们就能发送一个非同源的请求,但是如果就这样的话,我们没法传参,服务器并不知道我们要干嘛,所以接下来,把参数传给服务器,把src改一下
myScript.src='https://localhost:8000/test.js?name=美女&age=18'//快,马上,把那个18岁的美女信息给我
服务器那边是可以拿到我们url信息的,它自然也可以通过约定的方式拿到我们传过去的name和age,接下来怎么把数据传给前端是一个问题.这里我举一个简单的例子
现在有一个html,里面有个script标签,引入了一个test.js文件
<script src='./test.js' type="text/javascript"></script>
test.js里面的内容是
console.log('window',window)
那么当我们直接打开html,而不进行任何操作时
直接就打印出来了,说明script请求的资源,内部的代码会被当做js直接执行.也就是说,我如果已经有相关数据了就可以用类似的方式把相关的数据给处理一下.
let message = { name:'小花', age:18, address:'马栏山' } let fun= function(v){ console.log('window',v) } fun(message)
然后就可以打印出来
接下来的问题是,光打印没有.而且script标签里的东西是直接就执行了,我没法拿到.即使return 我也没办法接受.所以结合传参和直接执行的特点,我们可以传一个参数给后端,这个参数是一个函数.我们模拟一下,
前端:
let data = {} let fun= function(v){ data = v console.log('data',data) } let myScript = document.createElement('script') let bodyDom =document.getElementsByTagName('body')[0] myScript.src='./test.js?callback=fun' bodyDom.appendChild(myScript)
服务器
/**** * 通过某种方式拿到参数里的callback */ let message = { name:'小花', age:18, address:'马栏山' } callback(message)
结果
所以,前后端统一一个参数叫callback,后端通过写一个callback(data),把数据给前端,然后前端在callback这个参数的值函数时去接收数据就行.原始的JSONP请求只能是get请求
2.CORS跨域
(1)CORS跨域是和JSONP完全不同的方式.CORS出现的心路历程大概是
浏览器:"不行!我不让那些开发仔跨域,但他们老是钻我漏洞,利用JSONP进行跨域请求,既然这个跨域请求需求挺高,我提供一个专门的途径好了."
服务器:"那不行啊!你开门了,我这边咋办,JSONP的方式是需要我这边配合,我不给他写callback()他们也拿不到数据,你开后门了我就危险了"
浏览器:"那这样好了,我这边虽然给跨域请求开门,但是我告诉你们它是跨域请求,还把相关信息给你,你自己看看要不要给它数据好了."
服务器:"那我看行."
所以CORS跨域,浏览器这边会给请求加上标记.比如Origin:'https://g.wuen.com'.这就相当于告诉服务器,我这个请求是CORS请求,而且来源是"https://g.wuen.com",剩下的就交给服务器处理了.
(2)简单请求和非简单请求
为什么要做这个区分?因为web服务器,比如nginx.nginx本身就作为一个中间件,是会默认匿名者通过put上传文件,也会允许通过delete删除文件的.所以,面对这些请求就需要特殊对待.
简单请求:
1)请求类型为:GET/HEAD/POST
2)Content-Type的值为:text/plain、multipart/form-data、application/x-www-from-urlencoded
非简单请求(复杂请求)
不满足简单请求的请求
(3)简单请求的CORS请求
首先,浏览器发送ajax请求时,会给头部加上一个Origin,告诉服务器我这边是CORS请求,
然后,服务拿到之后判断是不是在白名单里面,是的话会在响应头里多几个参数,请求源地址(Access-Control-Allow-Origin )和是否允许传递COOKIE(Access-Control-Allow-Credentials
).如果请求源地址设置为*,Access-Control-Allow-Credentials
就不能为true.
最后需要说明,因为cookie是跟网站相关的,而且cookie是相对来说比较重要的信息,一般跨域并不会默认传递.除了响应头中Access-Control-Allow-Credentials
:true外,ajax请求中的withCredentials:true.前后端都同意才可传递cookie.
(4)非简单请求的CORS请求
由于类似put,delet请求及时web服务器不做响应,也能操作服务器文件.所以,非简单请求的CORS请求会在响应前直接操作.也就是说,我不能等你发了请求再做校验和限制.解决方案就是"预检"请求,发请求前先征得服务器同意,同意了再发正式请求.
1)"预检"请求
浏览器需要提供:
Origin:'https://www.baidu.com',//请求源网址
Access-Control-Request-Method:PUT, //请求方法,多个方法逗号隔开
Access-Control-Request-Headers:XXX, //自定义请求头特殊字段
"预检"本身是options请求
服务器响应
Access-Control-Request-Origin:'https://www.baidu.com',//请求源网址 Access-Control-Request-Method:PUT, //请求方法,多个方法逗号隔开 Access-Control-Request-Headers:XXX, //自定义请求头特殊字段 Access-Control-Request-age:1200 //本次预检有效期
以及其他一些
无论是哪种CORS跨域请求,浏览器只有拿到"Access-Control-Request-Origin"才认为请求成功,在非简单请求里,拿到改字段了,浏览器才会认为我这个请求服务器时同意我发的.然后才会发一个正式请求
2)正式请求
通过预检之后,正式请求就跟普通请求一样.但是此时的请求已经被服务器认为是安全的,也就是说就算你发put请求操作文件了也没关系.
3.代理跨域
因为跨域是个前端概念,所以我们可以让服务器去请求服务器就不会有这个问题.比如我们本地开发时用的proxy.
4.iframe跨域
当前页面嵌套一个跟目标url同源的页面,在页面内部调用接口,然后赋值给window对象,因为嵌套页面和当前页面公用一个window对应.
以及一些其他的方案.