什么是跨域
跨域是指一个域下的文档(脚本)视图去访问另一个域下的资源,这里的跨域是广义的
广义的跨域:
资源转跳:a连接、重定向、表单提交
资源嵌入:link标签 script标签 frame标签(html5不支持),还有样式中 background:url()、@font-face()等文件外链
脚本请求:JS发起的ajax请求,dom和JS对象的跨域操作等
狭义的跨域:
所谓狭义的跨域是由浏览器同源策略限制的一类请求场景
什么是同源策略
同源策略/SOP(Same origin policy) 是一种约定,由Netscape公司1995年引入浏览器,他是浏览器最核心也是最基本的安全功能,如果缺少了同源策略,浏览器很容易受到 XSS CSRF等攻击.所谓同源指的是"协议 + 域名 + 端口" 三者相同,即便两个不同的域名指向同一个ip地址,也非同源
关于xxs攻击/csrf攻击
https://www.cnblogs.com/wzj4858/p/8259944.html
https://baijiahao.baidu.com/s?id=1618267672561552800&wfr=spider&for=pc
同源策略限制了以下几种行为
1、cookie、LocalStorage(局部处理器,通过 JS 操作) 和 IndexDB(本地存储) 无法读取
2、DOM(HTML节点树) 和 JS 对象无法获得
3、AJAX 请求发送后,被浏览器拦截
但是有三个标签是允许跨域加载资源:
<img src='xxx'>
<link href='xxx'>
<script src='xxx'>
常见跨域场景
'当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。'
URL 说明 是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js 同一域名,不同文件或路径 允许
http://www.domain.com/lab/c.js
http://www.domain.com:8000/a.js
http://www.domain.com/b.js 同一域名,不同端口 不允许
http://www.domain.com/a.js
https://www.domain.com/b.js 同一域名,不同协议 不允许
http://www.domain.com/a.js
http://192.168.4.12/b.js 域名和域名对应相同ip 不允许
http://www.domain.com/a.js
http://x.domain.com/b.js 主域相同,子域不同 不允许
http://domain.com/c.js
http://www.domain1.com/a.js
http://www.domain2.com/b.js 不同域名 不允许
由此可得 当协议、子域名、主域名、端口号中任意一个不相同时,都算作不同域。不同域之间相互请求资源,就算作“跨域”。都会被浏览器拦截
特别说明两点:
第一:如果是协议和端口造成的跨域问题“前台”是无能为力的。
第二:在跨域问题上,仅仅是通过“URL的首部”来识别而不会根据域名对应的IP地址是否相同来判断。“URL的首部”可以理解为“协议, 域名和端口必须匹配”。
这里你或许有个疑问:请求跨域了,那么请求到底发出去没有?
跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。
常见解决跨域问题的方案
1.通过jsonp跨域
2.跨域资源共享(CORS)
3.nginx反向代理
一 通过jsonp跨域
通常为了减轻web服务器的负载,我们把js css img 等静态资源分离到另一台独立域名的服务器上,在HTML页面中通过相应的标签从不同域名下加载静态资源,而被浏览器允许,基于此原理,我们可以通过动态创建script,再请求一个带参网址实现跨域通信
jsonp缺点:
有局限性,只能实现get一种请求;
不安全,可能会遭受xss攻击
jsonp和ajax对比:
jsonp和ajax相同,都是客户端向服务端发送请求,从服务器获取数据的方式.但是ajax属于同源策略,jsonp属于非同源策略(跨域请求)
- 原生实现:
下面有两个端口 8080 和 8088 因为端口的不同,导致非同源,所以,一旦进行访问,就会被拦截
在前后端不分离的情况下:
访问 path('index/',onBack) index路由执行 onBack函数
函数:
def onBack(request):
print('成功')
return HttpResponse('少时诵诗书')
前端:
<script>
// 创建 script 标签 并指定类型
var script = document.createElement('script');
script.type = 'text/html';
// 指定路由 和 回调函数 callback=jsonCallback
script.src = 'http://127.0.0.1:8088/hahaha?callback=jsonCallback';
// 添加到 文件头中
document.body.appendChild(script);
// 回调函数
function jsonCallback(res) {
console.log(data);
}
</script>
注意:前端转跳的是域名 'hahaha' 端口 8088 并且执行了回调函数 jsonCallback()
前端页面成功的显示了 少时诵诗书 这个关键词
单纯的拿到数据并不能让我们满意。一个合适的请求函数,必然包含对成功、失败、超时的处理,就像我们上面写的那个简单示例,一旦出现异常,就不能让我们满意了。
在这一点上不得不说Jquery做的很好,Jquery的Jsonp函数包含了对各种情况的处理,还伪造了一个http状态码的返回。
jsonp和ajax不同 是拿不到状态码的,但是jQuery对于所有的错误都赋予了一个404的状态码对比其他的组件库(axios-jsonp, axios-jsonp-pro, jsonp, fetch=jsonp-es6), 要不就是完全没有对超时的处理,要不然就是把错误和超时混成一谭,更有甚者,有些都不能自定义callback函数的名字。这简直太过分了。
为什么我们不选择jQuery呢 : 因为太大了webpack引入jQuery后瞬间增大了80k,单独将jsonp打包出来也有70k,问我的源码只有20k,这是我无法接受的
- JQuery ajax
有了原生JS的对比,jQuery更是简单
// 引入 jQuery
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
<script>
$.ajax({
url: 'http://127.0.0.1:8080/hahaha', //发送请求
type: 'get',
dataType:'jsonp', // 请求方式为 jsonp
success: function (res) { // 执行回调函数
console.log(res)
}
});
</script>
- vue.js
1、安装 vue jsonp # npm install jsonp
2、在main.js中导入vue-jsonp # 导入jsonp import VueJsonp from 'vue-jsonp' 封装jsonp vue.use(VueJsonp)
3、编写代码 格式: this.$http.jsonp('url',[可选参数,使用{}传参]).then(成功回调函数,失败回调函数);
还有一个关于创建公共方法jsonp.js : vue_music:JSONP
二、 跨域资源共享(CORS)
跨来源资源共享(CORS)是一份浏览器技术的规范,提供了Web 服务从不同网域传来沙盒脚本的方法,以避开浏览器的同源策略,是JSONP 模式的现代版。与JSONP 不同,CORS 除了GET 要求方法以外也支持其他的HTTP 要求。用CORS 可以让网页设计师用一般的XMLHttpRequest,这种方式的错误处理比JSONP 要来的好。另一方面,JSONP 可以在不支持CORS 的老旧浏览器上运作。现代的浏览器都支持CORS。所以只要后端实现CORS就实现了跨域
虽然设置CORS和前端没有什么关系,但是通过这种方式解决问题的话会在发送请求时出现两种情况:简单请求 、复杂请求
- 简单请求: 满足以下两大条件就属于简单请求
请求方式:
GET
、POST
、HEAD 只请求页面首部
请求头信息:Content-Type的值仅限于三者之一:text/plain 纯文本
、multipart/form-data
、application/x-www-form-urlencoded
注意:请求中的任意XMLHttpRequestUpload
对象均没有注册任何时间监听器;XMLHttpRequestUpload
对象可以使用XMLHttpRequest.upload
属性访问
- 复杂请求
不属于简单请求的都是复杂请求
复杂请求在正式通信前都会先发送预检 ,该请求是option方法,通过该请求来指导服务端是否允许跨域请求. option 利用中间件加响应头解决跨域问题
from django.utils.deprecation import MiddlewareMixin
class MyCore(MiddlewareMixin): # 解决跨域问题
def process_response(self, request, response):
response["Access-Control-Allow-Origin"] = '*' # 解决简单请求的跨域问题
if request.method == 'OPTIONS': # 复杂请求会发送option预检;解决复杂请求的跨域问题
response["Access-Control-Allow-Headers"] = 'Content-Type'
response["Access-Control-Allow-Methods"] = 'POST,DELETE,PUT'
return response
对于自己前后端分离的项目,跨域只存在于测试阶段(前后端交互用json数据,这就是复杂请求),放到服务器不存在跨域问题,因为有nginx
一些常用的键值对
// 设置哪个源可以访问我
'Access-Control-Allow-Origin', origin
// 允许携带哪个头访问我
'Access-Control-Allow-Headers', 'name'
// 允许哪个方法访问我
'Access-Control-Allow-Methods', 'PUT'
// 允许携带cookie
'Access-Control-Allow-Credentials', true
// 预检的存活时间
'Access-Control-Max-Age', 6
// 允许返回的头
'Access-Control-Expose-Headers', 'name'
三 nginx反向代理
使用nginx反向代理实现跨域,是最简单的跨域方式。
图解:
在正向代理中,Proxy和Client同属于一个LAN(图中方框内),隐藏了客户端信息;
在反向代理中,Proxy和Server同属于一个LAN(图中方框内),隐藏了服务端信息;
实际上,Proxy在两种代理中做的事情都是替服务器代为收发请求和响应,不过从结构上看正好左右互换了一下,所以把后出现的那种代理方式称为反向代理了。
反向代理的作用:
(1)保证内网的安全,通常将反向代理作为公网访问地址,Web服务器是内网
(2)负载均衡,通过反向代理服务器来优化网站的负载
网友评论