Django 全局完全禁用CSRF机制
写在之前
- 只讲禁用
CSRF
方法,不讲CSRF
讲理 - 会记录
Django
中关于CSRF
常用的一些方法和类 - 以了解
Django
中间件为前提, 看以下内容
禁用方法
- 最简单也是网上推荐最多的方法,找到
settings.py => MIDDLEWARE
配置项 => 修改如下
MIDDLEWARE = [
...
"django.middleware.common.CommonMiddleware",
# "django.middleware.csrf.CsrfViewMiddleware", # 注释掉此行代码即可
"django.contrib.auth.middleware.AuthenticationMiddleware",
...
]
- 我推荐的写法,也是全局禁用最有效的方法,自定义中间件
middleware.py
, 如下
# tools/middleware.py
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger(__name__)
class CloseCsrfMiddleware(MiddlewareMixin):
def process_request(self, request):
request.csrf_processing_done = True # csrf处理完毕
logger.info("csrf全局禁用")
讲解
在上面两个方法中,如果你的目的是想全局禁用CSRF验证,我建议你使用第二个禁用方法,禁的彻彻底底的。如果你使用的是第一个方法,你会发现,当你使用admin
、xadmin
或其他第三方关于认证的插件时,CSRF
机制有时候还是会蹦出来做怪。为什么?为什么禁了没有效果?
我在使用xadmin
的时候就遇到这种情况,分明注释了django.middleware.csrf.CsrfViewMiddleware
,还是会提示CSRF
验证失败(失败原因我就不过多解释了,不同人遇到的情况不一样的,我这里是因为域名做了二次代理),以登录为例,xadmin
部分源码如下:
# xadmin/view/website.py
...
from django.contrib.auth.views import LoginView as login
...
class LoginView(BaseAdminView):
...
@never_cache
def get(self, request, *args, **kwargs):
context = self.get_context()
helper = FormHelper()
helper.form_tag = False
helper.include_media = False
context.update({
"title": self.title,
"helper": helper,
"app_path": request.get_full_path(),
REDIRECT_FIELD_NAME: request.get_full_path(),
})
defaults = {
"extra_context": context,
# "current_app": self.admin_site.name,
"authentication_form": self.login_form or AdminAuthenticationForm,
"template_name": self.login_template or "xadmin/views/login.html",
}
self.update_params(defaults)
# return login(request, **defaults)
return login.as_view(**defaults)(request)
@never_cache
def post(self, request, *args, **kwargs):
return self.get(request)
...
xadmin
登录时,后台方法调用如下:def post()
=> def get()
=> login.as_view()
; 其中, def post()
、def get()
为xadmin
下class LoginView()
内的方法;login.as_view()
为django
原生的登录验证类
django
原生的登录验证类源码如下:
# django/crontrab/auth/view.py
class LoginView(SuccessURLAllowedHostsMixin, FormView):
...
@method_decorator(sensitive_post_parameters())
@method_decorator(csrf_protect) #### 注意这行 ####
@method_decorator(never_cache)
def dispatch(self, request, *args, **kwargs):
if self.redirect_authenticated_user and self.request.user.is_authenticated:
redirect_to = self.get_success_url()
if redirect_to == self.request.path:
raise ValueError(
"Redirection loop for authenticated user detected. Check that "
"your LOGIN_REDIRECT_URL doesn"t point to a login page."
)
return HttpResponseRedirect(redirect_to)
return super().dispatch(request, *args, **kwargs)
注意这行代码@method_decorator(csrf_protect)
在这里你要知道的是,装饰器csrf_protect
的作用是进行CSRF
验证
所以,即使你注释了django.middleware.csrf.CsrfViewMiddleware
,在这里经过装饰器csrf_protect
还是会再次进行CSRF
验证。
真相终于大白了。
接下说说,第二种禁用CSRF
方法
通过查看@csrf_protect
源码(就不贴上来了)会发现,内部实现是,对class CsrfViewMiddleware
进行了实例化,然后依次调用了中间件中def process_request()
、def process_view()
等方法,其中,CsrfViewMiddleware.process_view()
,是进行CSRF
验证的逻辑,源码如下:
class CsrfViewMiddleware(MiddlewareMixin):
...
def process_view(self, request, callback, callback_args, callback_kwargs):
# 注意csrf_processing_done变量,这个变量很关键
# 这个变量目的是记录在本次请求中是否已经进行过CSRF校验
# 如果已经校验过了,就不再走下面的验证逻辑了。
if getattr(request, "csrf_processing_done", False):
return None
# 这一步是查看被调用的def view()方法是否加了@csrf_exempt装饰器
# 如果加了,就不再走下面的验证逻辑了。
# Wait until request.META["CSRF_COOKIE"] has been manipulated before
# bailing out, so that get_token still works
if getattr(callback, "csrf_exempt", False):
return None
# 下面就是CSRF的验证逻辑了
# Assume that anything not defined as "safe" by RFC7231 needs protection
if request.method not in ("GET", "HEAD", "OPTIONS", "TRACE"):
if getattr(request, "_dont_enforce_csrf_checks", False):
# Mechanism to turn off CSRF checks for test suite.
# It comes after the creation of CSRF cookies, so that
# everything else continues to work exactly the same
# (e.g. cookies are sent, etc.), but before any
# branches that call reject().
return self._accept(request)
...
如上所示, 第二种禁用CSRF
方法原理就是, 设置request.csrf_processing_done=True
。
致此,完!