django中间件及csrf跨站请求伪造相关知识
django中间件是django的门户,有两个特点:1.请求来的时候需要先经过中间件才能到达真正的django后端 2.响应走的时候最后也需要经过中间件才能发送出去。
django自带有七个中间件
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
将每一个中间件里面的字符串按照from django.middleware.security impot SecurityMiddleware的形式修改后点到最后导的包中,查看中间件是如何工作的,以及工作原理。研究三个中间件中的方法
class SessionMiddleware(MiddlewareMixin): def process_request(self, request): session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME) request.session = self.SessionStore(session_key) def process_response(self, request, response): return response
class CsrfViewMiddleware(MiddlewareMixin): 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): return self._accept(request) def process_response(self, request, response): return response
class AuthenticationMiddleware(MiddlewareMixin): def process_request(self, request): request.user = SimpleLazyObject(lambda: get_user(request))
django支持程序员自定义中间件并且暴露给程序员五个可以自定义的方法:
1.必须掌握
process_request
rocess_response
2.了解即可
process_view
process_template_response
process_exception
如何自定义中间件
1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内需要书写类(这个类必须继承MiddlewareMixin)然后在这个类里面就可以自定义五个方法了(这五个方法并不是全部都需要书写,用几个写几个)
4.需要将类的路径以字符串的形式注册到配置文件中才能生效
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', ... '你自己写的中间件的路径1', '你自己写的中间件的路径2', '你自己写的中间件的路径3', ]
先在项目中创建一个文件夹,然后文件夹中创建一个.py文件,在.py文件中书写中间件代码,如下:
#所有中间件的类都继承了MiddlewareMixin类,所以要对其进行导入 from django.utils.deprecation import MiddlewareMixin class MyMiddleware1(MiddlewareMixin): def process_request(self,request): print('我是第一个自定义中间件里面的process_request方法') # return HttpResponse('baby!') def process_response(self,request,response): """ :param request: :param response: 就是django后端返回给浏览器的内容 :return: """ print('我是第一个自定义中间件里面的process_response方法') return response def process_view(self,request,view_name,*args,**kwargs): print(view_name,args,kwargs) print('我是第一个自定义中间件里面的process_view') def process_template_response(self,request,response): print('我是第一个自定义中间件里面的process_template_response') return response def process_exception(self,request,exception): print('我是第一个中间件里面的process_exception') print(exception)
class MyMiddleware2(MiddlewareMixin): def process_request(self,request): print('我是第二个自定义中间件里面的process_request方法') def process_response(self,request,response): print('我是第二个自定义中间件里面的process_response方法') return response def process_view(self,request,view_name,*args,**kwargs): print(view_name,args,kwargs) print('我是第二个自定义中间件里面的process_view') def process_template_response(self,request,response): print('我是第二个自定义中间件里面的process_template_response') return response def process_exception(self,request,exception): print('我是第二个中间件里面的process_exception') print(exception)
1.必须掌握
process_request
1.请求来的时候需要经过每一个中间件里面的process_request方法,结果的顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行,而是直接原路返回(校验失败不允许访问...)
process_request方法就是用来做全局相关的所有限制功能
process_response
1.响应走的时候需要结果每一个中间件里面的process_response方法,该方法有两个额外的参数request,response
2.该方法必须返回一个HttpResponse对象
1.默认返回的就是形参response
2.你也可以自己返回自己的
3.顺序是按照配置文件中注册了的中间件从下往上依次经过,如果你没有定义的话 直接跳过执行下一个
思考:如果在第一个process_request方法就已经返回了HttpResponse对象,那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况
是其他情况,会直接走同级别的process_reponse返回
flask框架也有一个中间件但是它的规律是只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法
2.了解即可
process_view
路由匹配成功之后执行视图函数之前,会自动执行中间件里面的该方法,顺序是按照配置文件中注册的中间件从上往下的顺序依次执行
process_template_response
返回的HttpResponse对象有render属性的时候才会触发,顺序是按照配置文件中注册了的中间件从下往上依次经过,请求的视图函数中代码如下,必须要这样写!
def index(request): print('我是视图函数index') obj = HttpResponse('index') def render(): print('内部的render') return HttpResponse("O98K") obj.render = render return obj
process_exception
当视图函数中出现异常的情况下触发
顺序是按照配置文件中注册了的中间件从下往上依次经过
csrf跨站请求伪造:
先来举一个小例子:早期的钓鱼网站:骗子通过编写一个与正规银行一样的网站,用户通过访问该网站进行转账。用户在转账过程完成之后,提交的转账操作也的确是通过银行系统,用户的钱确实是减少了,但是却没有转到用户想要转入的账户中,而是转入了骗子的账户中,那么这个是怎么操作的呢。用户在进行转账的时候所提交的对方账户信息的iuput输入框中并没有name属性,用户只是输入了一串数字,而骗子账户的input标签中才含有真正的name和value属性可以传入后端。
那么如何规避上述问题呢?
利用csrf跨站对伪造的请求进行校验,网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加上一个唯一标识,当这个页面朝后端发送post请求的时候,我们的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden),如果成功则正常执行。
如何使得提交的含有用户输入数据信息在向后端发送post请求的时候含有能被后端识别的唯一标识呢?
后端views中代码:
def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s给%s转了%s元'%(username,target_user,money)) return render(request,'transfer.html')
form表单发送post请求
# form表单如何符合校验 <form action="" method="post"> {% csrf_token %} <p>username:<input type="text" name="username"></p> <p>target_user:<input type="text" name="target_user"></p> <p>money:<input type="text" name="money"></p> <input type="submit"> </form>
使用csrf模板标签,{% csrf_token %}
ajax提交请求
当使用ajax提交post请求的时候有三种办法可以使得提交的页面符合校验
第一种:利用标签查找获取页面上的随机字符串
<button id="d1">ajax请求</button> <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', // 第一种 利用标签查找获取页面上的随机字符串 data:{"username":'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()}, success:function () { } }) }) </script>
第二种:利用模版语法提供的快捷书写
<button id="d1">ajax请求</button> <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', // 第二种 利用模版语法提供的快捷书写 data:{"username":'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}, success:function () { } }) }) </script>
第三种:是大多数项目中的通用方式,直接拷贝js代码并应用到自己的html页面上即可,代码都已经被封装好了,直接拿过来用
js代码:
function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie !== '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]); // Does this cookie string begin with the name we want? if (cookie.substring(0, name.length + 1) === (name + '=')) { cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); break; } } } return cookieValue; } var csrftoken = getCookie('csrftoken'); function csrfSafeMethod(method) { // these HTTP methods do not require CSRF protection return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method)); } $.ajaxSetup({ beforeSend: function (xhr, settings) { if (!csrfSafeMethod(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRFToken", csrftoken); } } });
前端代码:
<button id="d1">ajax请求</button> {% load static %} <script src="{% static 'js/mysetup.js' %}"></script> <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', // 第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可 data:{"username":'jason'}, success:function (args) {
... } }) }) </script>
csrf相关装饰器
先提两个需求:
1.网站整体都不校验csrf(在配置文件中将中间件注释掉),就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
django中提前已经封装好了两个相关功能装饰器,使用之前需要先导入;
from django.views.decorators.csrf import csrf_protect,csrf_exempt from django.utils.decorators import method_decorator
csrf_protect:需要校验,针对csrf_protect符合我们之前所学的装饰器的三种玩法
csrf_exempt:忽视校验,针对csrf_exempt只能给dispatch方法加才有效
FBV形式:
#前端提交的页面中没有csrf模板标签,也没有将csrf中间件注释掉,但是后端对所提交数据不进行校验 @csrf_exempt def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s给%s转了%s元'%(username,target_user,money)) return render(request,'transfer.html')
#将csrf中间件注释掉,向后端提交数据的表单中含有csrf标签,需要对个别提交的页面进行校验 @csrf_protect def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s给%s转了%s元'%(username,target_user,money)) return render(request,'transfer.html')
CBV形式:
针对csrf_protect装饰器:符合前面讲的的三种装饰器的使用方式
# @method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以 class MyCsrfToken(View): # @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以 def dispatch(self, request, *args, **kwargs): return super(MyCsrfToken, self).dispatch(request,*args,**kwargs) def get(self,request): return HttpResponse('get') # @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以 def post(self,request): return HttpResponse('post')
针对csrf_exempt装饰器:
# @method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以 #@method_decorator(csrf_exempt,name='dispatch')第三种方式跟这种加装饰器的方法是一样的 class MyCsrfToken(View): # @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以 def dispatch(self, request, *args, **kwargs): return super(MyCsrfToken, self).dispatch(request,*args,**kwargs) def get(self,request): return HttpResponse('get') # @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以 def post(self,request): return HttpResponse('post')
补充内容:基于django中间件学习编程思想
import settings import importlib def send_all(content): for path_str in settings.NOTIFY_LIST: #'notify.email.Email' module_path,class_name = path_str.rsplit('.',maxsplit=1) # module_path = 'notify.email' class_name = 'Email' # 1 利用字符串导入模块 module = importlib.import_module(module_path) # from notify import email # 2 利用反射获取类名 cls = getattr(module,class_name) # Email、QQ、Wechat # 3 生成类的对象 obj = cls() # 4 利用鸭子类型直接调用send方法 obj.send(content)