CORS与Django
前言
在前后端分离项目中,如何解决跨域请求是一个必须要面对的问题。因为前端和后端的数据交互会被浏览器的同源策略所挟持,在很早之前我在博客园发了一篇文章,大概就说了一下如何简单粗暴的解决跨域。
其实那种解决办法是不适用于内部项目的,而是对一些公共的开发接口做访问进行数据获取,下面是文章链接:
CORS
CORS
就是跨域资源共享的意思,它需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS
通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS
通信与同源的AJAX
通信没有差别,代码完全一样。浏览器一旦发现AJAX
请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。
因此,实现CORS
通信的关键是服务器。只要服务器实现了CORS
接口,就可以跨源通信。
基本流程
浏览器将CORS
请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。 浏览器发出CORS
简单请求,只需要在头信息之中增加一个Origin
字段。 浏览器发出CORS
非简单请求,会在正式通信之前,增加一次HTTP
查询请求,称为”预检”请求(preflight)。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP
动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest
请求,否则就报错。
请求区别
只要同时满足以下两大条件,就属于简单请求,否则就是非简单请求。
-
请求方法是以下三种方法之一:
HEAD
GET
POST
- HTTP的头信息不超出以下几种字段:
处理情况
浏览器对于上面两种请求处理,会有不一样的方式:
简单请求
浏览器只会发送一次请求,然后后端将会返回数据,此时后端如果没有做跨域资源请求的允许,数据将会被浏览器同源策略所拦截。
非简单请求
浏览器会发送两次请求,第一次请求方式为OPTIONS
,大概就是做一个预检查,检查后端是否允许第二次请求的发送。
如果允许,才会发送第二次请求,后端会将第二次请求的数据返回,如果后端没有做跨域资源请求的允许,数据将会被浏览器同源策略所拦截。
如果不允许,浏览器就不会发送第二次请求。
后端解决
对于简单请求来说,我们只需要允许跨域资源共享即可。
所以只需要在后端设置一个请求头:
Access-Control-Allow-Origin = "域名或者*"
对于非简单请求来说,我们需要让预检进行通过,此外还需要允许跨域资源共享,所以需要设置最少设置两个或多个请求头:
- “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
- “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
# 这两个是让预检通过
Access-Control-Allow-Headers = "Content-Type"
Access-Control-Allow-Headers = "authorization" #JWT认证
# 下面这个是允许资源共享
Access-Control-Allow-Origin = "域名或者*"
基本总结
一言以蔽之,当前端跨域向后端发送、请求数据时,有可能发简单请求,也有可能发非简单请求。
简单请求只会发一次,而非简单请求会发两次请求。
如果是简单请求,数据就会直接返回,如果是非简单请求,则需要后端做出一些相应的设置才会允许第二次真正请求的发送。
对于非简单请求的预检来说,它其实是规定了前端能够去使用哪些请求方式,如GET/POST
,同时还允许请求头中有什么,如Content-Type
以及JWT
随机字符串。
我举个例子,如果前端要跨域通过POST
请求发送一个JSON
数据那么它的流程如下:
- 发送
OPTIONS
请求进行预检,检测是否允许带有Content-Type
请求头(因为JSON
格式必须由该请求头声明) - 预检通过后,发送
POST
请求,Content-Type
为JSON
,带上数据发送过去。
- 发送
Django解决
中间件
对于Django
来说,我们可以将处理设置在中间件中。不管你是使用drf还是原生Django,都可以这样设置,因为此时的request对象并未被包装。
下面是同时支持简单请求和非简单请求的示例:
from django.utils.deprecation import MiddlewareMixin
class CorsMiddle(MiddlewareMixin):
def process_response(self, request, response):
response['Access-Control-Allow-Origin'] = '*' # 允许跨域,解决同源策略。简单和非简单请求都需要设置这个,可以加地址,只允许这个地址的请求跨域,如HTTP://127.0.0.1:2333
if request.method == "OPTIONS":
# 可以加*,代表允许所有。 这里是预检的OPTIONS请求,它允许Content-Type请求头和authorization请求头。
response["Access-Control-Allow-Headers"] = "Origin,Content-Type,Cookie,Accept,Token,authorization"
return response
# 记得在setting的中间件中配置
django-cors-headers
这是一个模块,可以很好的解决同源策略问题,以及非简单请求的预检问题。
下载安装即可:
pip install django-cors-headers
然后需要进行注册:
INSTALLED_APPS = (
'corsheaders',
)
添加中间件(其实它的解决方案和上面写的思路完全一模一样):
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware', # 写上面,因为Django中间件的返回顺序是自下而上。
...
]
在settings.py
中进行配置:
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True # 允许异端请求,解决同源策略
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',
)