当前位置: 移动技术网 > IT编程>脚本编程>Python > 详解Django的CSRF认证实现

详解Django的CSRF认证实现

2018年11月03日  | 移动技术网IT编程  | 我要评论

神采炼金术梅斯特,靖州蓝色影院,火影忍者 博人传

什么是 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的错误

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装饰器的用法跟上面一样

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网