阅读:43
跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。 这里说明一下,无法跨域是浏览器对于用户安全的考虑,如果自己写个没有同源策略的浏览器,完全不用考虑跨域问题了。
域可以理解为:协议 + 域名 + 端口号
http://127.0.0.1:8080
后端运行在http://127.0.0.1:8000
端口,当前端发起请求时,因为域(端口不一致)不同,就会造成跨域,浏览器便会阻止该请求。跨域请求分为2种,一种是简单请求,一种是复杂请求。
只要同时满足以下两大条件,就属于简单请求
HTTP 方法是下列之一
- HEAD
- GET
- POST
HTTP 头信息不超出以下几种字段
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type 只能是下列中的一个类型
application/x-www-from-urlencoded
multipart/form-data
text/plain
任何一个不满足上述要求的请求,即会被认为是复杂请求
复杂请求会先发出一个预请求,我们也叫预检,OPTIONS请求
浏览器只阻止表单以及ajax请求,并不会阻止src请求,所以我们的cdn,图片等src请求都可以发~~
当我们向后端发起GET请求时,后端没有做跨域访问。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<title>跨域请求测试</title>
</head>
<body>
<div id="app">
<button @click="Send">发送请求</button>
</div>
</body>
<script>
new Vue({
el:"#app",
methods:{
Send(){
axios.get('http://127.0.0.1:8000/api/test/')
.then(function (response) {
// 处理成功情况
console.log(response);
})
.catch(function (error) {
// 处理错误情况
console.log(error);
})
}
}
})
</script>
</html>
浏览器给出提示。从origin 'null’访问’http://127.0.0.1:8000/api/test/'的XMLHttpRequest已经被CORS[跨域资源共享]策略阻止:没有’Access- control - allow - origin '头在请求的资源上存在。
我们在后端也发现,请求已经进来了,并成功返回了。所以数据被浏览器给劫持了
当我们使用img
script
标签的src来发起一个GET请求,会触发CORB(Cross-Origin Read Blocking )跨域读取阻塞。
当跨域请求回来的数据MIME type
同跨域标签应有的MIME
类型不匹配时,浏览器会启动 CORB 保护数据不被泄漏,被保护的数据类型只有html xml
和 json
。很明显 <script>
和 <img>
等跨域标签应有的MIME type
和html
、xml
、json
不一样。
详情可以看这篇文章 https://juejin.cn/post/6844903831373889550
在前面的测试时,我们发现请求已经到达,响应后被浏览器拦截了,所以需要我们告诉浏览器不要拦截
我这里使用的是django作为Web后端开发框架,当我们发起GET请求时,我们在后端添加浏览器所需要对应的请求体参数Access-Control-Allow-Origin
1.在TestView视图函数添加请求参数
def TestView(request):
data = {
"user": "jack",
"pwd": "123",
}
if request.method == 'GET':
return HttpResponse(json.dumps(data),content_type='application/json')
4.重新启动,在此进行测试
前端代码如上,测试成功
注意:在进行post测试时,为了方便,我们需要注释掉django的csrf中间件,这是django自带的防止跨域请求伪造。
增加一个post方法判断
def TestView(request):
data = {
"user": "jack",
"pwd": "123",
}
if request.method == 'GET': # 请求方法需要全部大写
return HttpResponse(json.dumps(data),content_type='application/json')
if request.method == 'POST':
return HttpResponse(json.dumps(data),content_type='application/json')
# settings.py django的配置文件下
MIDDLEWARE = [
......
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
......
]
详细了解csrf:https://juejin.cn/post/6844903653757698062.
复杂请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json
。
复杂请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"(OPTIONS)请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
PUT方法测试
method: 'put'
发送一个put请求过去。put
请求时,先送过来的是一个options
请求,浏览器数据请求失败了,我们需要先在options
请求的headers
中添加Access-Control-Allow-Origin
Access-Control-Allow-Origin
# 在原来的视图函数中增加
if request.method == 'OPTIONS':
return HttpResponse(headers={"Access-Control-Allow-Origin":"*"})
if request.method == 'PUT':
return HttpResponse(json.dumps(data))
Access-Control-Allow-Methods
,表示PUT方法不被允许。我们在PUT和OPTIONS的headers
中都再添加一个Access-Control-Allow-Methods
,然后前端继续发送PUTAccess-Control-Allow-Methods
if request.method == 'OPTIONS':
return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
if request.method == 'PUT':
return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
JSON数据测试
前端携带一个json数据,向后端发送post请求,因为Content-Type中不包含JSON格式的数据,所以该请求为复杂请求
6. 前端携带JSON数据
Send(){
axios({
method: 'post',
url: 'http://127.0.0.1:8000/api/test/',
data:{
"course_id":1,
"course_title":"中华上下五千年深度解说"
}
})
.then(response => {
console.log(response)
});
}
def TestView(request):
data = {
"user": "jack",
"pwd": "123",
}
print(request.method)
if request.method == 'GET':
return HttpResponse(json.dumps(data))
if request.method == 'POST':
return HttpResponse(json.dumps(data))
if request.method == 'OPTIONS':
return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
if request.method == 'PUT':
return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT"})
return HttpResponse("okokok")
Access-Control-Allow-Headers
if request.method == 'POST':
return HttpResponse(json.dumps(data),headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT","Access-Control-Allow-Headers":"content-type"})
if request.method == 'OPTIONS':
return HttpResponse(headers={"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"PUT","Access-Control-Allow-Headers":"content-type"})
在上述的函数中,通过定义headers来处理跨域的多种情况,过于繁琐。我们可以写一个中间件来统一处理跨域问题
1.创建CorsMiddleware.py
from django.utils.deprecation import MiddlewareMixin
class CorsMiddleware(MiddlewareMixin):
def process_response(self, request, response):
response["Access-Control-Allow-Origin"] = "*"
if request.method == "OPTIONS":
response["Access-Control-Allow-Headers"] = "Content-Type"
response["Access-Control-Allow-Methods"] = "DELETE, PUT, POST"
return response
2.在Settings.py 配置文件的中间件中添加
MIDDLEWARE = [
......
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'GOMusicApi.utils.cors_middleware.CorsMiddleware',
]
3. 重新书写视图,去掉所有的headers,重启项目
# 这里使用了rest_framework
from django.http import JsonResponse,HttpResponse
from rest_framework.views import APIView
class TestView(APIView):
def post(self,request):
print(request.body.decode('utf-8'))
data = {
"user": "jack",
"password": "12333",
}
return JsonResponse(data,safe=False)
# 这里是使用了def 函数来实现返回数据
def TestView(request):
print(request.method)
if request.method == 'POST':
print(request.body.decode('utf-8'))
data = {
"user": "jack",
"password": "12333",
}
return JsonResponse(data,safe=False)
return JsonResponse(data,safe=False)
# 这里return,因为先发送了options请求,故需要一个返回,不然后端会报没有返回对象的错误。
1. 安装
pip install django-cors-headers
2. 在settings文件中进行配置
INSTALLED_APPS = [
......
'django.contrib.staticfiles',
'corsheaders',
]
MIDDLEWARE_CLASSES = [
.....
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
......
]
3. Django-cors-Headers 配置介绍
在 Django 设置中配置中间件的行为。您必须至少设置以下三个设置之一:
CORS_ALLOWED_ORIGINS # 默认为空列表,设置允许发送http请求的域名
CORS_ALLOWED_ORIGIN_REGEXES # 允许域名列表使用正则模式
CORS_ALLOW_ALL_ORIGINS # 运行所有的域名访问
CORS_ALLOWED_ORIGINS
CORS_ALLOWED_ORIGINS = [
"https://example.com" ,
"https://sub.example.com" ,
"http://localhost:8080" ,
"http://127.0.0.1:9000" ,
]
以前此设置称为CORS_ORIGIN_WHITELIST
,它仍然作为别名使用,新名称优先 【注意是django3.2使用新名称】
CORS_ALLOWED_ORIGIN_REGEXES
一个字符串列表,表示与授权进行跨站点 HTTP 请求的 Origin 匹配的正则表达式。默认为[]。当 CORS_ALLOWED_ORIGINS
不切实际时很有用,例如当您有大量子域时。
CORS_ALLOWED_ORIGIN_REGEXES = [
r "^https://\w+\.example\.com$" ,
]
以前此设置称为CORS_ORIGIN_REGEX_WHITELIST
,它仍然作为别名使用,新名称优先。【注意是django3.2使用新名称】
CORS_ALLOW_ALL_ORIGINS
CORS_ALLOWED_ORIGINS
或 CORS_ALLOWED_ORIGIN_REGEXES
限制允许的来源列表。以前此设置称为CORS_ORIGIN_ALLOW_ALL
,它仍然作为别名使用,新名称优先。【注意是django3.2使用新名称】
这下面都是一些可选设置了,代码块中为默认值,一般默认值足够了
CORS_ALLOW_METHODS
实际请求允许的 HTTP 动词列表
CORS_ALLOW_METHODS = [
"DELETE" ,
"GET" ,
"OPTIONS" ,
"PATCH" ,
"POST" ,
"PUT" ,
]
CORS_ALLOW_HEADERS
发出实际请求时可以使用的非标准 HTTP 标头列表。
CORS_ALLOW_HEADERS = [
"accept" ,
"accept-encoding" ,
"authorization" ,
"content-type" ,
"dnt" ,
"origin" ,
"user-agent" ,
"x-csrftoken" ,
"x-requested-with" ,
]
CORS_ALLOW_CREDENTIALS
True
,cookie 将被允许包含在跨站点 HTTP 请求中。默认为False。注意:在 Django 2.1 中添加了SESSION_COOKIE_SAMESITE设置,默认设置为 “Lax”,这将阻止 Django 的会话 cookie 跨域发送。将其更改为无以绕过此安全限制。
详情链接:https://pypi.org/project/django-cors-headers/
4. 我的配置
# --------------------------------------- 跨域请求配置 cors ---------------------------------------#
CORS_ALLOW_CREDENTIALS = True # 允许携带cookie
# CORS_ORIGIN_ALLOW_ALL = True # 允许所有源访问
CORS_ORIGIN_WHITELIST = ( #设置白名单 CORS_ALLOWED_ORIGINS【django3.2】
'http://127.0.0.1:8000',
'http://localhost:8000',
'http://192.168.1.107:8080'
)
CORS_ALLOW_METHODS = ( #允许的方法
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
CORS_ALLOW_HEADERS = ( #允许的请求头
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
配置成功后,前端可以成功访问后端。
浏览器对不同域之间做出的行为,对应不同的请求(简单请求和复杂请求)做出了不同的拦截方式,也就解释了为什么要添加请求参数。
Access-Control-Allow-Origin
:为了解决域不一致的问题;
Access-Control-Allow-Methods
:为了解决PUT,DELETE等方法
Access-Control-Allow-Headers
:为了解决JSON数据传输的问题