django 接口跨域问题和解决
背景
需要在一个 django 的项目中,添加一组暴露接口,使该接口可以直接接口请求而不是浏览器访问
问题现象
通过外部接口请求,比如 postman 或者 apifox 发起接口请求时,接口返回:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="robots" content="NONE,NOARCHIVE">
<title>403 Forbidden</title>
<style type="text/css">
html * {
padding: 0;
margin: 0;
}
body * {
padding: 10px 20px;
}
body * * {
padding: 0;
}
body {
font: small sans-serif;
background: #eee;
color: #000;
}
body>div {
border-bottom: 1px solid #ddd;
}
h1 {
font-weight: normal;
margin-bottom: .4em;
}
h1 span {
font-size: 60%;
color: #666;
font-weight: normal;
}
#info {
background: #f6f6f6;
}
#info ul {
margin: 0.5em 4em;
}
#info p,
#summary p {
padding-top: 10px;
}
#summary {
background: #ffc;
}
#explanation {
background: #eee;
border-bottom: 0px none;
}
</style>
</head>
<body>
<div id="summary">
<h1>Forbidden <span>(403)</span></h1>
<p>CSRF verification failed. Request aborted.</p>
<p>You are seeing this message because this site requires a CSRF cookie when submitting forms. This cookie is
required for security reasons, to ensure that your browser is not being hijacked by third parties.</p>
<p>If you have configured your browser to disable cookies, please re-enable them, at least for this site, or for
'same-origin' requests.</p>
</div>
<div id="info">
<h2>Help</h2>
<p>Reason given for failure:</p>
<pre>
CSRF cookie not set.
</pre>
<p>In general, this can occur when there is a genuine Cross Site Request Forgery, or when
<a href="
https://docs.djangoproject.com/en/2.2/ref/csrf/
">Django's
CSRF mechanism</a> has not been used correctly. For POST forms, you need to
ensure:
</p>
<ul>
<li>Your browser is accepting cookies.</li>
<li>The view function passes a <code>request</code> to the template's <a href="
https://docs.djangoproject.com/en/dev/topics/templates/
#django.template.backends.base.Template.render"><code>render</code></a>
method.</li>
<li>In the template, there is a <code>{% csrf_token
%}</code> template tag inside each POST form that
targets an internal URL.</li>
<li>If you are not using <code>CsrfViewMiddleware</code>, then you must use
<code>csrf_protect</code> on any views that use the <code>csrf_token</code>
template tag, as well as those that accept the POST data.
</li>
<li>The form has a valid CSRF token. After logging in in another browser
tab or hitting the back button after a login, you may need to reload the
page with the form, because the token is rotated after a login.</li>
</ul>
<p>You're seeing the help section of this page because you have <code>DEBUG =
True</code> in your Django settings file. Change that to <code>False</code>,
and only the initial error message will be displayed. </p>
<p>You can customize this page using the CSRF_FAILURE_VIEW setting.</p>
问题原因
Django 应用在处理 POST 请求时遇到了 CSRF(跨站请求伪造,Cross-Site Request Forgery)验证失败的问题。具体来说,错误信息指出“CSRF cookie not set”,意味着请求中没有找到预期的 CSRF cookie,这是 Django 为了防止跨站请求伪造攻击而采取的安全措施
解决方案
Django 默认并不允许跨域请求,为了允许从 Postman 或其他外部域名发起的 POST 请求,你需要设置 CORS 策略
使用 django-cors-headers
- 安装
pip install django-cors-headers
注意和 django 的版本对应,不同版本的 django-cors-headers 所依赖的 django 版本不同,具体可以参考:https://pypi.org/project/django-cors-headers/3.0.0/
- 注册
将
corsheaders
添加到INSTALLED_APPS
列表中。
添加 CORS 中间件到中间件列表中,并确保它位于其他中间件之前,以便它可以最早处理 CORS 头部。
可以全局允许所有来源,或者指定允许的来源列表。
# settings.py
INSTALLED_APPS = [
# ...
'corsheaders',
# ...
]
MIDDLEWARE = [
# CORS Headers middleware should be placed as high as possible
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
# ...
]
CORS_ORIGIN_ALLOW_ALL = True # 这将允许所有来源的请求。在生产环境中,你可能需要更严格的控制,比如列出具体的允许来源。
# 或者,你可以指定允许的源列表:
# CORS_ALLOWED_ORIGINS = [
# "http://localhost:3000",
# "https://example.com",
# ]
注意的就是 cors-headers 的中间件CorsMiddleware 在注册时必须放在 django-common 中间件的前一个。
但是这种方式我最终没成功,目前原因没找到。
使用装饰器 @csrf_exempt
- 导入
from django.views.decorators.csrf import csrf_exempt
- 使用
@csrf_exempt
def notify_success(request):
# 你可以根据需要处理请求数据,这里简化处理直接返回成功信息
return HttpResponse('success')
@csrf_exempt
def notify_failure(request):
# 同样,这里简化处理直接返回失败信息
return HttpResponse("failure")
使用该方式成功,apifox 再发起请求后,请求成功,跨域问题不存在。