django中间件原理
class MiddlewareMixin:
#django启动时就执行,与用户无关
def __init__(self, get_response=None):
self.get_response = get_response
super().__init__()
def __call__(self, request):
response = None
if hasattr(self, 'process_request'):
response = self.process_request(request)
#request到达views之前执行,如果在process_request函数中有返回值一般就会直接返回的不会执行以后的中间件
response = response or self.get_response(request)
#后面的中间件和views执行完成后返回
if hasattr(self, 'process_response'):
response = self.process_response(request, response)
return response
例子:csrf_token:csrf原理:https://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html
推荐一篇关与csrf中间件的文章 https://blog.csdn.net/qq_27952549/article/details/82392790
CSRF攻击的过程
从cookie中取出csrf_token
从表单数据中取出隐藏的csrf_token的值
将这两个值进行比对
token值更新速度快,值保存在前端,cookie中和要提交表单中,然后后端进行比较这两值。
生成cookie
def get_token(request):
"""
Return the CSRF token required for a POST form. The token is an
alphanumeric value. A new token is created if one is not already set.
A side effect of calling this function is to make the csrf_protect
decorator and the CsrfViewMiddleware add a CSRF cookie and a 'Vary: Cookie'
header to the outgoing response. For this reason, you may need to use this
function lazily, as is done by the csrf context processor.
"""
#如果request.META中没有CSRF_COOKIE,就会给添加一个CSRF_COOKIE字段
if "CSRF_COOKIE" not in request.META:
csrf_secret = _get_new_csrf_string()
request.META["CSRF_COOKIE"] = _salt_cipher_secret(csrf_secret)
else:
#如果有CSRF_COOKIE就解密
csrf_secret = _unsalt_cipher_token(request.META["CSRF_COOKIE"])
request.META["CSRF_COOKIE_USED"] = True
return _salt_cipher_secret(csrf_secret)
给response设置cookie
def _set_token(self, request, response):
if settings.CSRF_USE_SESSIONS:
request.session[CSRF_SESSION_KEY] = request.META['CSRF_COOKIE']
else:
response.set_cookie(
settings.CSRF_COOKIE_NAME,
request.META['CSRF_COOKIE'],
max_age=settings.CSRF_COOKIE_AGE,
domain=settings.CSRF_COOKIE_DOMAIN,
path=settings.CSRF_COOKIE_PATH,
secure=settings.CSRF_COOKIE_SECURE,
httponly=settings.CSRF_COOKIE_HTTPONLY,
samesite=settings.CSRF_COOKIE_SAMESITE,
)
# Set the Vary header since content varies with the CSRF cookie.
patch_vary_headers(response, ('Cookie',))
在中间件执行process_response方法时,给request设置cookie
def process_response(self, request, response):
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.
self._set_token(request, response)
response.csrf_cookie_set = True
return response
使用中间件生成token
from django.middleware.csrf import get_token
from django.utils.deprecation import MiddlewareMixin
class Middleware(MiddlewareMixin):
def process_request(self,request):
get_token(request)
我前面是把csrf_token的中间件给注释了
我们可以看到在中间件中有这样一个函数
class CsrfViewMiddleware(MiddlewareMixin):
def _get_token(self, request):
if settings.CSRF_USE_SESSIONS:
try:
return request.session.get(CSRF_SESSION_KEY)
except AttributeError:
raise ImproperlyConfigured(
'CSRF_USE_SESSIONS is enabled, but request.session is not '
'set. SessionMiddleware must appear before CsrfViewMiddleware '
'in MIDDLEWARE%s.' % ('_CLASSES' if settings.MIDDLEWARE is None else '')
)
else:
try:
cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
except KeyError:
return None
csrf_token = _sanitize_token(cookie_token)
if csrf_token != cookie_token:
# Cookie token needed to be replaced;
# the cookie needs to be reset.
request.csrf_cookie_needs_reset = True
return csrf_token
#在views.py之前进行
def process_request(self, request):
csrf_token = self._get_token(request)
if csrf_token is not None:
# Use same token next time.
request.META['CSRF_COOKIE'] = csrf_token
def process_view(self, request, callback, callback_args, callback_kwargs):
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'):
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)
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 = request.META.get('HTTP_REFERER')
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:
try:
# request.get_host() includes the port.
good_referer = request.get_host()
except DisallowedHost:
pass
# Create a list of all acceptable HTTP referers, including the
# current host if it's permitted by ALLOWED_HOSTS.
good_hosts = list(settings.CSRF_TRUSTED_ORIGINS)
if good_referer is not None:
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)
csrf_token = request.META.get('CSRF_COOKIE')
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:
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
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)
if not _compare_salted_tokens(request_csrf_token, csrf_token):
return self._reject(request, REASON_BAD_TOKEN)
return self._accept(request)
1、我们看到在process_request函数中会拿到cookie中的csrf-token值,然后在process_view函数中判断post等修改数据请求中拿到另一个token值,得到前端传来的request_csrf_token的值,因此我们必须得到前端传来token,不然就会报错,生成token的方式有很多 比如配合form的{%csrf_token%} 也可以设置全局的token值
这里使用中间件生成token值,作用与所有视图和模板
2、django中的cookie中的csrf_token由CsrfViewMiddleware设置,如果尚未在请求中设置csrf令牌,则它将与调用django.middleware.csrf.get_token()的每个响应一起发送(该函数在内部用于检索csrf令牌)。
所有传出的POST表单中都存在一个名称为’csrfmiddlewaretoken’的隐藏表单字段。该字段的值再次是机密的值,带有一个掩码,该掩码既被添加到该密码,又被用于加扰。每次调用时都会重新生成掩码,get_token()以便在每个此类响应中都更改表单字段值。这部分是通过template标签完成的
为了防止突破口攻击,令牌不仅仅是秘密;在秘密前加上一个随机掩码,用于对其进行置乱。
出于安全原因,每次用户登录时都会更改机密值。
对于所有未使用HTTP GET,HEAD,OPTIONS或TRACE的传入请求,必须存在CSRF cookie,并且“ csrfmiddlewaretoken”字段必须存在且正确。如果不是,则用户将收到403错误。
为什么用户登录后可能会遇到CSRF验证失败?¶
出于安全原因,每次用户登录时都会旋转CSRF令牌。任何在登录之前生成表单的页面都将具有旧的无效CSRF令牌,需要重新加载。如果用户在登录后使用后退按钮,或者他们登录了其他浏览器选项卡,则可能会发生这种情况。
1.process_request方法中:
从请求的cookie中获取csrftoken的值 ——》csrf_token ——》request.META['CSRF_COOKIE']
2.process_view方法中:
1. 如果视图函数加上了csrf_exempt的装饰器 不做校验
这就是为什么要在process_view中进行csrf_token的校验,而不是在process_request进行校验,process_request执行完之前还没有进行路由匹配,无法知道那个视图不需要进行csrf_token的校验。
2. 如果请求方式是'GET', 'HEAD', 'OPTIONS', 'TRACE' 也不做校验
3. 其他的请求方式做校验
request.META.get('CSRF_COOKIE') —— 》 csrf_token # 拿到csrf_token
request_csrf_token = "" # 取值有两种方法
1.
request_csrf_token = request.POST.get('csrfmiddlewaretoken', '') # 从request.POST中获取csrfmiddlewaretoken对应的值
2.
request_csrf_token = request.META.get(settings.CSRF_HEADER_NAME, '') # 从请求头中获取X-csrftoken 的值
request_csrf_token 和 csrf_token 进行对比校验
如果校验成功 正常走
如果校验不成功 拒绝
fbv 可以使用csrf_exempt装饰器用来逃避csrf验证,
而cbv需要写在dispatch方法加装饰器才行。
或: