django---django-cors-headers跨域源码分析
Posted 进击的小猿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django---django-cors-headers跨域源码分析相关的知识,希望对你有一定的参考价值。
库配置
这篇笔记,是关于跨越的 django—CORS跨域
今天我们来学习下一个开源库django-cors-headers
github上有详细的配置文档说明
1、Install from pip:
pip install django-cors-headers
2、and then add it to your installed apps:
INSTALLED_APPS = (
...
'corsheaders',
...
)
3、You will also need to add a middleware class to listen in on responses:
MIDDLEWARE = [ # Or MIDDLEWARE_CLASSES on Django < 1.10
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
CORSMIDID餐具应尽可能高的放置,尤其是在任何能产生响应的中间件之前,比如Django的通用中间件或白化的白皮书。如果不是以前,它将无法向这些响应添加CORS头。
此外,如果使用CLSYRePaseHTTPSY引用器,它应该放在Django的CSRFIEWVIEW中间件之前
4、在Django设置中配置中间件的行为。必须将允许跨站点请求的主机添加到CLSIOrthPosixWalelistor,或者将CLSSOrthPosialApple全部设置为true,以允许所有主机。
CORS_ORIGIN_ALLOW_ALL = True
5、另外还有一些白名单、正则url配置,我这里就省略了,我就直接CORS_ORIGIN_ALLOW_ALL = True
示例展示
我接下来利用一个示例,来展示下功能效果
项目配置:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api.apps.ApiConfig',
'rest_framework',
'corsheaders',
]
from corsheaders.middleware import CorsMiddleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
CORS_ORIGIN_ALLOW_ALL = True
vue前端代码
contentType: “application/json”发送j’son数据
doLogin()
var _this = this
$.ajax(
url: "http://127.0.0.1:8000/api/login/",
type: 'post',
data: JSON.stringify(
"username": this.username,
"password": this.password,
),
dataType: 'json',
contentType: "application/json",
// headers:
// "Content-Type": 'application/json',
// ,
success: function (newAllDatas)
console.info(newAllDatas, "~~~~~~newAllDatas~~~~~~~~")
if (newAllDatas.data.token)
_this.$store.commit('saveToken', username: newAllDatas.data.username, token: newAllDatas.data.token)
_this.$router.push(name: 'index')
else
_this.error = '用户名或密码错误'
)
后端api
class LoginView(APIView):
def generate_key(self,account):
"""根据用户名和时间生成唯一标识"""
username = account.username
now = str(datetime.datetime.now()).encode('utf-8')
md5 = hashlib.md5(username.encode('utf-8'))
md5.update(now)
return md5.hexdigest()
# def options(self, request, *args, **kwargs):
# print('进行预检')
# obj = HttpResponse()
# # obj['Access-Control-Allow-Headers'] = 'Content-Type'
# # obj['Access-Control-Allow-Origin'] = '*'
# return obj
def post(self,request,*args,**kwargs):
try:
response = BaseResponse()
with transaction.atomic():
print(request.data,"request,data")
print(request._request.POST,"request,POST")
account = Account.objects.create(username=request.data.get("username"),password=request.data.get("password"))
if account:
token = self.generate_key(account)
u_token = UserAuthToken.objects.create(user=account,token=token,created=datetime.datetime.now())
if u_token:
usdata = UserSerializer(instance=account)
response.data = usdata.data
response.error = "1000"
response.msg = "登录成功"
ret = Response(response.dict)
# ret['Access-Control-Allow-Origin'] = '*'
print("[response.dict]", response.dict)
return ret
except IndexError as e:
response.error = "2000"
response.msg = "登录失败"
ret = Response(response.dict)
# ret['Access-Control-Allow-Origin'] = '*'
print("[Exception type %s][response.dict%s]"%(str(e),response.dict))
return ret
源码分析
我先贴出来该开源库的目录结构
在该库的__init__.py
模块下from .checks import check_settings
导入该模块
在checks.py
中经过@checks.register
进行装饰过滤,主要是判断我们的配置有没有问题,例如
我们之前在配置文件中进行的配置CORS_ORIGIN_ALLOW_ALL = True
,是否是bool
类型的
if not isinstance(conf.CORS_ORIGIN_ALLOW_ALL, bool):
errors.append(
checks.Error(
"CORS_ORIGIN_ALLOW_ALL should be a bool.",
id="corsheaders.E005"
)
)
该checks.py
模块头部,还引入from .conf import conf
,我们看看这个配置是做什么处理工作的?
我这里就列举出来,该类中的某些方法,我们依然拿CORS_ORIGIN_ALLOW_ALL = True
举例,该类的主要左用就是通过反射作用,获取配置的值
class Settings(object):
@property
def CORS_ORIGIN_ALLOW_ALL(self):
return getattr(settings, 'CORS_ORIGIN_ALLOW_ALL', False)
conf.py
模块中,头部引入了from .defaults import default_headers, default_methods
是对请求头、及其请求方式做了列举
default_headers = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
)
default_methods = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
)
我们接下来就看下middleware.py
如何实现跨域的
还记得我们在配置中配置的中间件么
'corsheaders.middleware.CorsMiddleware'
代码不多,我就直接贴出来了
class CorsMiddleware(MiddlewareMixin):
def _https_referer_replace(self, request):
"""
When https is enabled, django CSRF checking includes referer checking
which breaks when using CORS. This function updates the HTTP_REFERER
header to make sure it matches HTTP_HOST, provided that our cors logic
succeeds
"""
origin = request.META.get('HTTP_ORIGIN')
if request.is_secure() and origin and 'ORIGINAL_HTTP_REFERER' not in request.META:
url = urlparse(origin)
if not conf.CORS_ORIGIN_ALLOW_ALL and not self.origin_found_in_white_lists(origin, url):
return
try:
http_referer = request.META['HTTP_REFERER']
http_host = "https://%s/" % request.META['HTTP_HOST']
request.META = request.META.copy()
request.META['ORIGINAL_HTTP_REFERER'] = http_referer
request.META['HTTP_REFERER'] = http_host
except KeyError:
pass
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
request._cors_enabled = self.is_enabled(request)
if request._cors_enabled:
if conf.CORS_REPLACE_HTTPS_REFERER:
self._https_referer_replace(request)
if (
request.method == 'OPTIONS' and
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
):
return http.HttpResponse()
def process_view(self, request, callback, callback_args, callback_kwargs):
"""
Do the referer replacement here as well
"""
if request._cors_enabled and conf.CORS_REPLACE_HTTPS_REFERER:
self._https_referer_replace(request)
return None
def process_response(self, request, response):
"""
Add the respective CORS headers
"""
origin = request.META.get('HTTP_ORIGIN')
if not origin:
return response
enabled = getattr(request, '_cors_enabled', None)
if enabled is None:
enabled = self.is_enabled(request)
if not enabled:
return response
# todo: check hostname from db instead
url = urlparse(origin)
if conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'
if (
not conf.CORS_ORIGIN_ALLOW_ALL and
not self.origin_found_in_white_lists(origin, url) and
not self.origin_found_in_model(url) and
not self.check_signal(request)
):
return response
if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
patch_vary_headers(response, ['Origin'])
if len(conf.CORS_EXPOSE_HEADERS):
response[ACCESS_CONTROL_EXPOSE_HEADERS] = ', '.join(conf.CORS_EXPOSE_HEADERS)
if request.method == 'OPTIONS':
response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
if conf.CORS_PREFLIGHT_MAX_AGE:
response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE
return response
def origin_found_in_white_lists(self, origin, url):
return (
url.netloc in conf.CORS_ORIGIN_WHITELIST or
(origin == 'null' and origin in conf.CORS_ORIGIN_WHITELIST) or
self.regex_domain_match(origin)
)
def regex_domain_match(self, origin):
for domain_pattern in conf.CORS_ORIGIN_REGEX_WHITELIST:
if re.match(domain_pattern, origin):
return origin
def origin_found_in_model(self, url):
if conf.CORS_MODEL is None:
return False
model = apps.get_model(*conf.CORS_MODEL.split('.'))
return model.objects.filter(cors=url.netloc).exists()
def is_enabled(self, request):
return (
bool(re.match(conf.CORS_URLS_REGEX, request.path)) or
self.check_signal(request)
)
def check_signal(self, request):
signal_responses = check_request_enabled.send(
sender=None,
request=request,
)
return any(
return_value for
function, return_value in signal_responses
)
我们就简单分析下即可
先经过process_request
,先看下这个类方法
def process_request(self, request):
"""
If CORS preflight header, then create an
empty body response (200 OK) and return it
Django won't bother calling any other request
view/exception middleware along with the requested view;
it will call any response middlewares
"""
request._cors_enabled = self.is_enabled(request)
if request._cors_enabled:
if conf.CORS_REPLACE_HTTPS_REFERER:
self._https_referer_replace(request)
if (
request.method == 'OPTIONS' and
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
):
return http.HttpResponse()
process_request
代码意思就是通过is_enabled
方法,通过request.path
进行正则判断是否是可用的url
,
然后通过 conf.CORS_REPLACE_HTTPS_REFERER
(默认falsel),来进行解决CSRFYtrutrudDoad源文图,
ps如下:
在Jjango 1.9中引入了CSRFYtrutrudDoad源,因此早期版本的用户需要一个替代的解决方案。如果COSYRePaseJTHPSPSJORKER是真的,CORSMIDIDWORKES会将引用头更改为当CORS检查通过时将通过Django的CSRF检查的东西。默认为false。
然后判断如下代码,当请求添加自定义头部,或者contentType为json(复杂请求),会执行OPTIONS
,我们需要预检信息
if (
request.method == 'OPTIONS' and
'HTTP_ACCESS_CONTROL_REQUEST_METHOD' in request.META
):
return http.HttpResponse()
然后直接进入该中间件的process_response
,在该方法里面,也就是进行了具体的跨域处理
跨域传输cookie如下
if conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_CREDENTIALS] = 'true'
允许不同源的链接访问
if conf.CORS_ORIGIN_ALLOW_ALL and not conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
patch_vary_headers(response, ['Origin'])
OPTIONS进行预检
if request.method == 'OPTIONS':
response[ACCESS_CONTROL_ALLOW_HEADERS] = ', '.join(conf.CORS_ALLOW_HEADERS)
response[ACCESS_CONTROL_ALLOW_METHODS] = ', '.join(conf.CORS_ALLOW_METHODS)
if conf.CORS_PREFLIGHT_MAX_AGE:
response[ACCESS_CONTROL_MAX_AGE] = conf.CORS_PREFLIGHT_MAX_AGE
以上就是基本的源码分析
以上是关于django---django-cors-headers跨域源码分析的主要内容,如果未能解决你的问题,请参考以下文章