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

class CloseCsrfMiddleware(MiddlewareMixin):

    def process_request(self, request):
        request.csrf_processing_done = True         # csrf处理完毕

讲解

在上面两个方法中,如果你的目的是想全局禁用CSRF验证,我建议你使用第二个禁用方法,禁的彻彻底底的。如果你使用的是第一个方法,你会发现,当你使用adminxadmin或其他第三方关于认证的插件时,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()xadminclass 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

posted @ 2020-12-04 09:50  ugvibib  阅读(1284)  评论(8编辑  收藏  举报