详解Django的CSRF认证
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装饰器的用法跟上面一样
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
时间太少,如何阅读?
国庆长假,没有到处跑,闲在家里读读书。看了一下我在豆瓣标记为 “想读” 的书籍已经突破了 300 本,而已标记读过的书才一百多本,感觉是永远读不完了。 好早以前我这个 “想读” 列表是很短的,一般不超过 20 本,因为以前我看见这个列表太长了后,就会主动停止往里面再添加了,直到把它们读完了,这样倒是有助于缓解下这种读不完的压力与焦虑感。 但后来渐渐想明白这个方法其实有很大的弊端,因为这样的处理算法是先进先出的,而更好的选择应该是按优先级队列来的。所以,后来我只要遇到好书,都往列表力放,只是在取的时候再考虑优先级,而不再对队列的长度感到忧虑。 说到这里,也给大家推荐一个架构交流学习群:835544715,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,相信对于已经工作和遇到技术瓶颈的码友,在这个群里会有你需要的内容。 那么从队列中取的时候,优先级算法是如何的呢?这就和每一个人具体的阅读偏好和习惯有关了。而我的阅读习惯简单可以用两个词来概...
- 下一篇
Android组件化开发实践(六):老项目实施组件化
比较早期的时候,我们开发APP都是采用单一工程模式,随着业务的发展,APP越来越庞大,开发人员越来越多,所以必然面临着将老项目进行组件化的过程。 在将老项目进行组件化的过程中,会面临很多的问题,以我自己的经验为例,主要有以下点: 代码年久失修,文档缺失,不敢随意修改,否则会牵一发而动全身,引起现有正常业务的运行; 进行组件化重构需要花费比较长的时间,业务不可能停下来等着你去重构; 组件化重构后,需要全量回归测试,测试比较花费时间; 相信每个人都会碰到这些问题,组件化重构势在必行,但是老板不可能专门给你几个月,啥业务都不做就让你进行代码重构。那怎么办呢,我这里讲讲自己的一些经验,供大家参考。 1. 优先集成路由框架 首先,在老项目中引入路由框架,不管是自己简单设计的也好,还是采用第三方框架,新开发的功能页面跳转一律采用路由,老的页面跳转逐步替换。这样做的目的是尽量减少代码耦合,为后面进行模块拆分打下基础。 2. 业务模块拆分 一个老项目必然经过很多人的手,你不一定要对所有代码都很熟悉,但是你必须要基本了解所有的业务功能,在此基础上对所有业务进行初步的模块划分。这个业务模块划分,粒度可以粗...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8编译安装MySQL8.0.19
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7,CentOS8安装Elasticsearch6.8.6
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果