浏览器的同源策略
浏览器有一个同源策略,它会阻止非同源的请求。非同源是指 域名不同 或者 端口不同。浏览同源策略只对 表单、ajax请求生效,不同拦截src的请求。
例如: 我在 127.0.0.1:8000 的页面 去调用 127.0.0.1:8001/api/test , 就会被浏览器拦截,因为端口不同,属于非同源请求。
src请求是指这种标签中带有src属性的请求:
<script src="https://cdn.bootcss.com/twitter-bootstrap/3.4.1/js/bootstrap.min.js"></script>
<img src="http:/127.0.0.1:8082/media/pic/5cm.jpg">
什么是 CORS 请求?
CROS (cross origin resource share): 跨域资源共享。
请求方 与 被请求资源 为 非同源的请求就是 CORS 请求,也叫做跨域请求,分为两种:
- 简单请求
1. http 方法 为 HEAD / GET / POST
2. http 请求头不超出这几种字段(可以没有但不能超出)
Accept, Accept-Language, Content-Language, Content-Type
其中 Content-Type 只能是以下几种类型:
application/x-www-from-urlencoded
multipart/form-data
text/plain
-
复杂请求
不满足简单请求条件的都属于复杂请求。
解决跨域问题
跨域问题重现
- 在 django 中开一个后端接口 http://127.0.0.1:8000/api/test
#新增路由
from app01.views import Cors_test
path('api/test', Cors_test.as_view(),),
# 新增视图函数
from rest_framework.views import APIView
from rest_framework.response import Response
class Cors_test(APIView):
def get(self,request):
return Response('ok')
-
在前端代码中使用 ajax 调用后端的接口
- 以服务器的方式打开前端代码文件 http://127.0.0.1:5500/templates/test.html ,并触发ajax请求,跨域报错
JSONP
jsonp 就是将需要跨域的链接作为src请求获取后端资源,避免跨域拦截。这样的方式有一些局限性,只能发送get请求,现在一般不用了。例如:
html 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" referrerpolicy="no-referrer"></script>
</head>
<body>
<button id="btn1">click</button>
<script type="text/javascript">
// 定义一个handle函数,供后端调用传参
function handle(data){
// 这里写你需要的操作
alert(data)
}
</script>
<!-- 调用后端接口,后端返回的内容将作为js代码执行 -->
<script type="text/javascript" src='http://127.0.0.1:8000/api/test'></script>
</body>
</html>
后端的视图函数
class Cors_test(View):
def get(self,request):
# 这里的handle('ok')传到前端将会看作js代码执行,即执行html的handle函数
response = HttpResponse("handle('ok')")
return response
访问测试:
vscode 安装 live Server插件,使我们的html文件可以以服务器的方式打开。
理想情况下现在应该是alert('ok'),跳出一个ok的弹窗。但是这里报了一个CORB的错误。
什么是CORB?
全称为 Cross-Origin Read Blocking,跨域读取阻止,浏览器在加载可以跨域资源时,在资源载入页面之前,对其进行识别和拦截的算法:
这个算法中有一个要点和本文相关:
-
如果 response 包含 X-Content-Type-Options: nosniff 响应头部,下面两种情况的请求将被阻止:
- 请求类型是"
style
” 但是 MIME 类型不是 “text/css
”, - 请求类型是"
script
” 但是 MIME 类型不是JavaScript MIME 类型。
请求类型我们可以看请求头中的Sec-Fetch-Dest, MIME 类型可以看返回头的 Content-Type, 请求的数据类型和返回的数据类型不一致,就很容易被 CORB机制拦截。
- 请求类型是"
怎么解决此处CORB的拦截问题?
将 Content-Type 请求头 设置为 text/javascript;charset=UTF-8 或者 X-Content-Type-Options 请求头设置为空。
所以视图函数的中配置:
class Cors_test(APIView):
def get(self,request):
response = HttpResponse("handle('ok')")
# response['X-Content-Type-Options'] = ""
response['Content-Type']="text/javascript;charset=UTF-8"
return response
看一下效果:
添加请求头
在 django 中,自定义中间件,给返回的response添加请求头。
对于简单请求,添加 Access-Control-Allow-Origin 请求头即可:
因为简单请求的请求方法以及请求头只要允许跨域,就默认允许携带。
from django.utils.deprecation import MiddlewareMixin
class Mymiddleware(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:5500'
return response
对于复杂请求:
例如ajax中使用put方法,会报错:
复杂请求时,浏览器会先发一个预检请求 ,就是这个options,这里报错 put请求不被允许。
ajax中添加简单请求中没有的请求头 contentType: 'application/json' 时,也会报错
所以就需要在中间件中添加 allow headers 与 allow methods
class Mymiddleware(MiddlewareMixin):
def process_response(self, request, response):
# 简单请求只需加这一个响应头
response['Access-Control-Allow-Origin'] = 'http://127.0.0.1:5500'
# 复杂请求还需额外添加对应的响应头
if request.method == 'OPTIONS':
response["Access-Control-Allow-Methods"] = "PUT,PATCH,DELETE"
response["Access-Control-Allow-Headers"] = "Content-Type"
return response
此时前端请求代码为:
<button id="btn1">click</button>
<script type="text/javascript">
$('#btn1').click(
function(){
$('#send').attr('src','http://127.0.0.1:8000/api/test')
$.ajax({
// put方法 --复杂请求
method: 'put',
url: 'http://127.0.0.1:8000/api/test',
// 'application/json'格式 --复杂请求
contentType: 'application/json',
success: function(data){
console.log(data)
}
})
}
)
</script>
后端视图函数代码为:
class Cors_test(APIView):
def get(self,request):
response = HttpResponse('ok')
return response
def put(self,request):
response = HttpResponse('put ok')
return response
访问效果: