详解Django的CSRF认证实现
什么是 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装饰器的用法跟上面一样
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。