详解Django的CSRF认证实现


Posted in Python onOctober 09, 2018

什么是 CSRF

CSRF, Cross Site Request Forgery, 跨站点伪造请求。举例来讲,某个恶意的网站上有一个指向你的网站的链接,如果某个用户已经登录到你的网站上了,那么当这个用户点击这个恶意网站上的那个链接时,就会向你的网站发来一个请求,你的网站会以为这个请求是用户自己发来的,其实呢,这个请求是那个恶意网站伪造的。

1.csrf原理

csrf要求发送post,put或delete请求的时候,是先以get方式发送请求,服务端响应时会分配一个随机字符串给客户端,客户端第二次发送post,put或delete请求时携带上次分配的随机字符串到服务端进行校验

2.Django中的CSRF中间件

首先,我们知道Django中间件作用于整个项目。

在一个项目中,如果想对全局所有视图函数或视图类起作用时,就可以在中间件中实现,比如想实现用户登录判断,基于用户的权限管理(RBAC)等都可以在Django中间件中来进行操作

Django内置了很多中间件,其中之一就是CSRF中间件

MIDDLEWARE_CLASSES = [
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

上面第四个就是Django内置的CSRF中间件

3.Django中间件的执行流程

Django中间件中最多可以定义5个方法

  • process_request
  • process_response
  • process_view
  • process_exception
  • process_template_response

Django中间件的执行顺序

1.请求进入到Django后,会按中间件的注册顺序执行每个中间件中的process_request方法
    如果所有的中间件的process_request方法都没有定义return语句,则进入路由映射,进行url匹配
    否则直接执行return语句,返回响应给客户端

2.依次按顺序执行中间件中的process_view方法
    如果某个中间件的process_view方法没有return语句,则根据第1步中匹配到的URL执行对应的视图函数或视图类
    如果某个中间件的process_view方法中定义了return语句,则后面的视图函数或视图类不会执行,程序会直接返回

3.视图函数或视图类执行完成之后,会按照中间件的注册顺序逆序执行中间件中的process_response方法
    如果中间件中定义了return语句,程序会正常执行,把视图函数或视图类的执行结果返回给客户端
    否则程序会抛出异常

4.程序在视图函数或视图类的正常执行过程中
    如果出现异常,则会执行按顺序执行中间件中的process_exception方法
    否则process_exception方法不会执行
    如果某个中间件的process_exception方法中定义了return语句,则后面的中间件中的process_exception方法不会继续执行了

5.如果视图函数或视图类中使用render方法来向客户端返回数据,则会触发中间件中的process_template_response方法

4.Django CSRF中间件的源码解析

Django CSRF中间件的源码

class CsrfViewMiddleware(MiddlewareMixin):

 def _accept(self, request):
  request.csrf_processing_done = True
  return None

 def _reject(self, request, reason):
  logger.warning(
   'Forbidden (%s): %s', reason, request.path,
   extra={
    'status_code': 403,
    'request': request,
   }
  )
  return _get_failure_view()(request, reason=reason)

 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

 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,
   )
   patch_vary_headers(response, ('Cookie',))

 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

  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:
     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)

   csrf_token = request.META.get('CSRF_COOKIE')
   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)

 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

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

从上面的源码中可以看到,CsrfViewMiddleware中间件中定义了process_request,process_view和process_response三个方法

先来看process_request方法

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

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

从Django项目配置文件夹中读取 CSRF_USE_SESSIONS 的值,如果获取成功,则 从session中读取CSRF_SESSION_KEY的值 ,默认为 '_csrftoken' ,如果没有获取到 CSRF_USE_SESSIONS 的值,则从发送过来的请求中获取 CSRF_COOKIE_NAME 的值,如果没有定义则返回None。

再来看process_view方法

在process_view方法中,先检查视图函数是否被 csrf_exempt 装饰器装饰,如果视图函数没有被csrf_exempt装饰器装饰,则程序继续执行,否则返回None。接着从request请求头中或者cookie中获取携带的token并进行验证,验证通过才会继续执行与URL匹配的视图函数,否则就返回 403 Forbidden 错误。

实际项目中,会在发送POST,PUT,DELETE,PATCH请求时,在提交的form表单中添加

{% csrf_token %}

即可,否则会出现403的错误

详解Django的CSRF认证实现

5.csrf_exempt装饰器和csrf_protect装饰器

5.1 基于Django FBV

在一个项目中,如果注册起用了 CsrfViewMiddleware 中间件,则项目中所有的视图函数和视图类在执行过程中都要进行CSRF验证。

此时想使某个视图函数或视图类不进行CSRF验证,则可以使用 csrf_exempt 装饰器装饰不想进行CSRF验证的视图函数

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt 
def index(request): 
 pass

也可以把csrf_exempt装饰器直接加在URL路由映射中,使某个视图函数不经过CSRF验证

from django.views.decorators.csrf import csrf_exempt 
 
from users import views 
 
urlpatterns = [ 
 url(r'^admin/', admin.site.urls), 
 url(r'^index/',csrf_exempt(views.index)), 
]

同样的,如果在一个Django项目中,没有注册起用 CsrfViewMiddleware 中间件,但是想让某个视图函数进行CSRF验证,则可以使用 csrf_protect 装饰器

csrf_protect装饰器的用法跟csrf_exempt装饰器用法相同 ,都可以加上视图函数上方装饰视图函数或者在URL路由映射中直接装饰视图函数

from django.views.decorators.csrf import csrf_exempt 

@csrf_protect 
def index(request): 
 pass

或者

from django.views.decorators.csrf import csrf_protect 
 
from users import views 
 
urlpatterns = [ 
 url(r'^admin/', admin.site.urls), 
 url(r'^index/',csrf_protect(views.index)), 
]

5.1 基于Django CBV

上面的情况是基于Django FBV的,如果是基于Django CBV,则不可以直接加在视图类的视图函数中了

此时有三种方式来对Django CBV进行CSRF验证或者不进行CSRF验证

方法一,在视图类中定义dispatch方法,为dispatch方法加csrf_exempt装饰器

from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator

class UserAuthView(View):

 @method_decorator(csrf_exempt)
 def dispatch(self, request, *args, **kwargs):
  return super(UserAuthView,self).dispatch(request,*args,**kwargs)

 def get(self,request,*args,**kwargs):
  pass

 def post(self,request,*args,**kwargs):
  pass

 def put(self,request,*args,**kwargs):
  pass

 def delete(self,request,*args,**kwargs):
  pass

方法二:为视图类上方添加装饰器

@method_decorator(csrf_exempt,name='dispatch')
class UserAuthView(View):
 def get(self,request,*args,**kwargs):
  pass

 def post(self,request,*args,**kwargs):
  pass

 def put(self,request,*args,**kwargs):
  pass

 def delete(self,request,*args,**kwargs):
  pass

方式三:在url.py中为类添加装饰器

from django.views.decorators.csrf import csrf_exempt

urlpatterns = [
 url(r'^admin/', admin.site.urls),
 url(r'^auth/', csrf_exempt(views.UserAuthView.as_view())),
]

csrf_protect装饰器的用法跟上面一样

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python开发WebService系列教程之REST,web.py,eurasia,Django
Jun 30 Python
Python reduce()函数的用法小结
Nov 15 Python
基于sklearn实现Bagging算法(python)
Jul 11 Python
PyTorch中Tensor的拼接与拆分的实现
Aug 18 Python
树莓派3 搭建 django 服务器的实例
Aug 29 Python
利用rest framework搭建Django API过程解析
Aug 31 Python
python tkinter canvas使用实例
Nov 04 Python
Python嵌套函数,作用域与偏函数用法实例分析
Dec 26 Python
pytorch模型存储的2种实现方法
Feb 14 Python
Python run()函数和start()函数的比较和差别介绍
May 03 Python
使用Keras建立模型并训练等一系列操作方式
Jul 02 Python
Python使用xpath实现图片爬取
Sep 16 Python
浅析python中的迭代与迭代对象
Oct 08 #Python
实例讲解python中的序列化知识点
Oct 08 #Python
实例讲解python中的协程
Oct 08 #Python
详解python分布式进程
Oct 08 #Python
python中多个装饰器的执行顺序详解
Oct 08 #Python
使用EduBlock轻松学习Python编程
Oct 08 #Python
Django forms组件的使用教程
Oct 08 #Python
You might like
PHP错误WARNING: SESSION_START() [FUNCTION.SESSION-START]解决方法
2014/05/04 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
PHP pear安装配置教程
2016/05/14 PHP
PHP记录和读取JSON格式日志文件
2016/07/07 PHP
thinkPHP订单数字提醒功能的实现方法
2016/12/01 PHP
php查看一个变量的占用内存的实例代码
2020/03/29 PHP
Thinkphp5框架中引入Markdown编辑器操作示例
2020/06/03 PHP
JS 遮照层实现代码
2010/03/31 Javascript
使用jQuery异步加载 JavaScript脚本解决方案
2014/04/20 Javascript
浅谈javascript构造函数与实例化对象
2015/06/22 Javascript
JavaScript事件 "事件对象"的注意要点
2016/01/14 Javascript
AngularJS基础 ng-mouseleave 指令详解
2016/08/02 Javascript
老生常谈javascript变量的命名规范和注释
2016/09/29 Javascript
详解vue.js数据传递以及数据分发slot
2018/01/20 Javascript
NW.js 简介与使用方法
2018/02/01 Javascript
Vue如何实现响应式系统
2018/07/11 Javascript
Javascript之高级数组API的使用实例
2019/03/08 Javascript
Python浅拷贝与深拷贝用法实例
2015/05/09 Python
Django应用程序中如何发送电子邮件详解
2017/02/04 Python
Python使用filetype精确判断文件类型
2017/07/02 Python
python:print格式化输出到文件的实例
2018/05/14 Python
对Python中9种生成新对象的方法总结
2018/05/23 Python
关于Python形参打包与解包小技巧分享
2019/08/24 Python
Python3 shelve对象持久存储原理详解
2020/03/23 Python
Python pytesseract验证码识别库用法解析
2020/06/29 Python
Python修改DBF文件指定列
2020/12/19 Python
美国南部最大的家族百货公司:Belk
2017/01/30 全球购物
英国花园药房: The Garden Pharmacy
2017/12/28 全球购物
MATCHESFASHION.COM美国官网:英国奢侈品零售商
2018/10/29 全球购物
实习自我鉴定模板
2013/09/28 职场文书
大学本科毕业生求职简历的自我评价
2013/10/09 职场文书
动画设计系毕业生求职信
2014/07/15 职场文书
2014领导班子四风问题查摆思想汇报
2014/09/13 职场文书
党员批评与自我批评思想汇报
2014/10/08 职场文书
自主招生自荐信范文
2015/03/04 职场文书
2015年学校政教处工作总结
2015/05/26 职场文书