csrf的中间件

Posted douzi-m

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了csrf的中间件相关的知识,希望对你有一定的参考价值。

csrf的中间件

源码简略分析:

    def process_request(self, request):
        # 从cookies中获取csrf_token
        csrf_token = self._get_token(request)
        if csrf_token is not None:
            # Use same token next time.
            # 将csrf_token添加到request的头部信息中
            request.META['CSRF_COOKIE'] = csrf_token

    def process_view(self, request, callback, callback_args, callback_kwargs):
        # csrf校验已经完成
        if getattr(request, 'csrf_processing_done', False):
            return None

        # 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

        # Assume that anything not defined as 'safe' by RFC7231 needs protection
        # 如果请求方式不在其中
        if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
            # 如果不需要进行csrf校验
            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)
            # 如果请求方式是https
            if request.is_secure():
                # Suppose user visits http://example.com/
                # An active network attacker (man-in-the-middle, MITM) sends a
                # POST form that targets https://example.com/detonate-bomb/ and
                # submits it via javascript.
                #
                # The attacker will need to provide a CSRF cookie and token, but
                # that's no problem for a MITM and the session-independent
                # secret we're using. So the MITM can circumvent the CSRF
                # protection. This is true for any HTTP connection, but anyone
                # using HTTPS expects better! For this reason, for
                # https://example.com/ we need additional protection that treats
                # http://example.com/ as completely untrusted. Under HTTPS,
                # Barth et al. found that the Referer header is missing for
                # same-domain requests in only about 0.2% of cases or less, so
                # we can use strict Referer checking.
                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)

                # Make sure we have a valid URL for Referer.
                if '' in (referer.scheme, referer.netloc):
                    return self._reject(request, REASON_MALFORMED_REFERER)

                # Ensure that our Referer is also secure.
                if referer.scheme != 'https':
                    return self._reject(request, REASON_INSECURE_REFERER)

                # If there isn't a CSRF_COOKIE_DOMAIN, require an exact match
                # match on host:port. If not, obey the cookie rules (or those
                # for the session cookie, if CSRF_USE_SESSIONS).
                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()

                # Here we generate a list of all acceptable HTTP referers,
                # including the current host since that has been validated
                # upstream.
                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)
            # 从MATA中获取'CSRF_COOKIE'(在process_request中设置的)
            csrf_token = request.META.get('CSRF_COOKIE')
            # csrf_token是空,校验失败
            if csrf_token is None:
                # No CSRF cookie. For POST requests, we insist on a CSRF cookie,
                # and in this way we can avoid all CSRF attacks, including login
                # CSRF.
                return self._reject(request, REASON_NO_CSRF_COOKIE)

            # Check non-cookie token for match.
            request_csrf_token = ""
            if request.method == "POST":
                try:
                    # 在POST提交的数据中获取'csrfmiddlewaretoken'
                    request_csrf_token = request.POST.get('csrfmiddlewaretoken', '')
                except IOError:
                    # Handle a broken connection before we've completed reading
                    # the POST data. process_view shouldn't raise any
                    # exceptions, so we'll ignore and serve the user a 403
                    # (assuming they're still listening, which they probably
                    # aren't because of the error).
                    pass
            # 如果POST数据中没有,则尝试从MATA中获取'x-csrftoken'
            if request_csrf_token == "":
                # Fall back to X-CSRFToken, to make things easier for AJAX,
                # and possible for PUT/DELETE.
                request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '')

            request_csrf_token = _sanitize_token(request_csrf_token)
            # 如果request_csrf_token和csrf_token不匹配,则校验失败
            if not _compare_salted_tokens(request_csrf_token, csrf_token):
                return self._reject(request, REASON_BAD_TOKEN)

        return self._accept(request)

    def process_response(self, request, response):
        # 如果不需要需要重新设置csrftoken
        if not getattr(request, 'csrf_cookie_needs_reset', False):
            if getattr(response, 'csrf_cookie_set', False):
                return response

        if not request.META.get("CSRF_COOKIE_USED", False):
            return response

        # Set the CSRF cookie even if it's already set, so we renew
        # the expiry timer.
        # 依据META中的'CSRF_COOKIE'的值来设置csrftoken
        self._set_token(request, response)
        response.csrf_cookie_set = True
        return response

csrf相关的装饰器:

from django.views.decorators.csrf import csrf_exempt, csrf_protect
# csrf_exempt  豁免csrf校验
# csrf_protect 强制进行csrf校验

from django.utils.decorators import method_decorator
@method_decorator(csrf_exempt, name='dispatch')
# csrf_exempt要加在CBV上,只能加dispatch上

csrf校验:

/1.想要能通过csrf校验的前提条件: 必须要有 csrftokencookie

  1. {% csrf_token %},在POST请求数据中添加csrfmiddlewaretoken
  2. from django.views.decorators.csrf import ensure_csrf_cookie

/2..从cookie获取csrftoken的值 与 POST提交的数据中的csrfmiddlewaretoken的值做对比(如果从request.POST中获取不到csrfmiddlewaretoken的值,会尝试从请求头META中获取x-csrftoken的值)拿这个值与csrftoken的值做对比,对比成功通过校验。

以上是关于csrf的中间件的主要内容,如果未能解决你的问题,请参考以下文章

csrf的中间件

django 解决csrf跨域问题

CSRF 令牌丢失或不正确

Laravel CSRF 中间件未检查 X-CSRF-TOKEN 请求标头

44. CSRF 攻击与防御

148.CSRF攻击原理分析防御装饰器中间件IFrame以及js实现csrf攻击