美文网首页
Django CsrfViewMiddleware 源码学习

Django CsrfViewMiddleware 源码学习

作者: vckah | 来源:发表于2018-08-20 21:42 被阅读0次

    这个中间件主要是防止 CSRF 攻击的。
    首先来总体概括一下:

    csrf_token 是一个全局变量,是 django 自动生成的,csrf_token 不需要 django 存储。每次刷新生成一个新的 token,然后将 token 返回给前端,同时伴随着 set-cookie 操作,把 token 写进浏览器 cookie。验证请求的时候,只需拿 cookie 里面的 csrftoken 和 csrf_token 和提交表单里面的 csrfmiddlewaretoken 值进行比较即可。如果一样,即请求有效。这里的比较不是看两个值是否相等,而是通过一系列解密判断的。

    接着来看一看源码:

    class CsrfViewMiddleware(MiddlewareMixin):
        def _accept(self, request):
            pass
        def _reject(self, request, reason):
            pass
        def _get_token(self, request):
            pass
        def _set_token(self, request, response):
            pass
        def process_view(self, request, callback, callback_args, callback_kwargs):
            pass
        def process_response(self, request, response):
            pass
    

    主要是 process_viewprocess_response,同样,view 主要是在 url 匹配之后,确定视图函数之前,而 response 是在执行完视图函数之后。接下来来看一下具体流程:

    def process_view(self, request, callback, callback_args, callback_kwargs):
        if getattr(request, 'csrf_processing_done', False):
            return None
    
        csrf_token = self._get_token(request)
        if csrf_token is not None:
            # Use same token next time.
            request.META['CSRF_COOKIE'] = csrf_token
    
        if getattr(callback, 'csrf_exempt', False):
            return None
    
        if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
            if getattr(request, '_dont_enforce_csrf_checks', False):
                return self._accept(request)
    
            if request.is_secure():
                referer = force_text(
                    request.META.get('HTTP_REFERER'),
                    strings_only=True,
                    errors='replace'
                )
                if referer is None:
                    return self._reject(request, REASON_NO_REFERER)
    
                referer = urlparse(referer)
    
                if '' in (referer.scheme, referer.netloc):
                    return self._reject(request, REASON_MALFORMED_REFERER)
    
                if referer.scheme != 'https':
                    return self._reject(request, REASON_INSECURE_REFERER)
    
                good_referer = (
                    settings.SESSION_COOKIE_DOMAIN
                    if settings.CSRF_USE_SESSIONS
                    else settings.CSRF_COOKIE_DOMAIN
                )
                if good_referer is not None:
                    server_port = request.get_port()
                    if server_port not in ('443', '80'):
                        good_referer = '%s:%s' % (good_referer, server_port)
                else:
                    # request.get_host() includes the port.
                    good_referer = request.get_host()
    
                good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
                good_hosts.append(good_referer)
    
                if not any(is_same_domain(referer.netloc, host) for host in good_hosts):
                    reason = REASON_BAD_REFERER % referer.geturl()
                    return self._reject(request, reason)
    
            if csrf_token is None:
                return self._reject(request, REASON_NO_CSRF_COOKIE)
    
            request_csrf_token = ""
            if request.method == "POST":
                try:
                    request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
                except IOError:
                    pass
    
            if request_csrf_token == "":
                request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')
    
            request_csrf_token = _sanitize_token(request_csrf_token)
            if not _compare_salted_tokens(request_csrf_token, csrf_token):
                return self._reject(request, REASON_BAD_TOKEN)
    
        return self._accept(request)
    

    这里我们思考一下为什么实现了 process_view 呢?process_view 是在 匹配 url 后准备执行视图函数前才调用的。因为某些 url 对应的视图函数里不准备使用 csrf 验证,那么验证这步干什么呢?
    首先判断 request 中有没有 csrf_processing_done ,这个是干什么的呢。然后判断回调函数时候是否有 csrf_exempt,因为这个属性代表不需要 csrf token 验证。
    然后 _get_token 里面判断有没有 token,有的话比对一下,否则返回 None。接着判断视图函数是否需要 csrf 防护,如果没有的话,当然也不用分析了。
    接着判断 HTTP 方法,如果不是 ('GET', 'HEAD', 'OPTIONS', 'TRACE') 这几种,那么继续执行。因为 rfc 定义这几种方法都是安全的。然后关闭测试套件的 csrf 检查,这里不明白。

    下来判断是否是 https 请求,这里进行了一些检查,说实话,这里也没看懂。

    接下来就是去 request.META 去获取 CSRF_COOKIE,如果没有则返回 403 页面。接着如果是 POST 请求就去 request.POST 中获取 csrfmiddlewaretoken 值,这里有可能会报错吗?如果没有,则会可能是其它请求或者是 AJAX 请求,则去 request.META 中去取,然后这里进行了 _sanitize_token 函数,主要是检查 request_csrf_token 是否符合规范
    详情如下

    def _sanitize_token(token):
        # Allow only ASCII alphanumerics
        if re.search('[^a-zA-Z0-9]', token):
            return _get_new_csrf_token()
        elif len(token) == CSRF_TOKEN_LENGTH:
            return token
        elif len(token) == CSRF_SECRET_LENGTH:
            return _salt_cipher_secret(token)
        return _get_new_csrf_token()
    

    如果不合规,直接构造一个新的 csrf_token,如果合规,返回原 csrf_token
    最后比较两个值,一个是表单里面的,一个是请求头中的 cookiecsrf_token。这里检验的话,有一个好办法,在浏览器中查看请求头和提交的参数。这里里面使用和 hmac 来验证。具体的可以跟踪看看。返回真就进入 _accept 函数,里面奇怪定义了equest.csrf_processing_done = True,不解,望大佬赐教。如果不相等,则返回 403 页面。

    下来看一看 process_response:

    def process_response(self, request, response):
        self._set_token(request, response)
        response.csrf_cookie_set = True
        return response
    

    这里是核心部分,至于检查那些参数,我也不是很懂。这里 _set_token,然后进去 response.set_cookie 操作,设置 cookie 值为 csrftoken
    好了,就到这里吧,里面还有一些细节没有掌握,例如 Django 随时在判断 request 和 response 的状态,我对状态还有一些不清楚,望大佬赐教。

    相关文章

      网友评论

          本文标题:Django CsrfViewMiddleware 源码学习

          本文链接:https://www.haomeiwen.com/subject/wffipftx.html