day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

ssshon

jdango中间件

django中间件是django的门户,在 用django 做开发的时候,只要是涉及到全局相关的功能都能用中间件方便的完成,如:

  • 全局用户身份校验
  • 全局用户权限校验
  • 全局访问频率校验

django 请求生命周期流程图:

https://img2020.cnblogs.com/blog/1952495/202005/1952495-20200527182357538-1283971585.png

django自带七个中间件,每个中间件都有各自对应的功能,相当于七个保安。

  • 请求来的时候必须要先经过中间件才能到达真正的jdango后端
  • 响应走的时候在最后也必须经过中间件才能发送出去。
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',
]

MIDDLEWARE列表里存放的不只是简单的字符串,它其实是一个个用于导入模块文件路径,最后一个点前面全是路径,最后一个点后的为具体导入的类名,如:

'django.contrib.sessions.middleware.SessionMiddleware'
from django.contrib.sessions.middleware import SessionMiddleware

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

django还支持程序员直接自定义中间件,并且暴露给程序员五个可以自定义的方法,接下来我们看看中间件内部的源码,查找规律,从而学习如何使用中间件自定义中间件。

# 操作django_session表的中间件
class SessionMiddleware(MiddlewareMixin):
    def process_request(self, request):
		pass
    def process_response(self, request, response):
        pass
  

# 跨网站访问伪造校验   
class CsrfViewMiddleware(MiddlewareMixin):
  	def process_request(self, request):
        pass
    def process_response(self, request, response):
        pass

    
# 认证相关     
class AuthenticationMiddleware(MiddlewareMixin):
    def process_request(self, request):
        pass

由上面三个中间件的可以看成,中间用的是面向对象,都继承MiddlewareMixin,而且在类里基本上都有这process_request process_request这两个方法,这两个方法也是我们重点要掌握的。

process_request  # 处理请求

process_response  # 处理响应

另外中件件的类还有三个需要了解的方法:

process_view  # 处理视图函数

process_template_response  # 处理页面响应

process_exception  # 处理异常

紧接着我们学习如果自定义django 中间件

如何自定义中间件

具体可分为四步:

  1. 在django项目下或者应用下创建一个任意名称的文件夹。

  2. 在该文件名下在创建任意名称的py文件

  3. 在该py文件内需要书写类(这个类必须继承MiddlewareMixin),然后在这个类里面就可以自定义五个方法了(并不是要求全写,需要用到哪个方法才写)

  4. 需要将类的路径以字符串的形式注册到配置文件中才能生效,就跟默认的七个中间件一样。

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        '...'
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
        '你自己写的中间件的路径1',
        '你自己写的中间件的路径2',
    ]
    

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

process_request方法

  • 请求来的时候需要经过每一个中间件的process_request方法,必须要有request参数。
  • 执行的顺序是安装settings配置文件中注册的顺序从上往下依次执行
  • 如果中间件中没有该方法,那么直接跳过,执行下一个中间件的process_request方法
  • 如果在执行到某一个中间件的process_request方法中返回来HttpRespones对象(三板斧), 那么请求就不再往后继续执行,而是直接原路往回,视图函数也就不会执行。

该方法多用于做全局相关的限制功能,比如用户的身份校验。

process_response方法

  • 响应走的时候需要经过每一个中间件中的rocess_response方法,该方法必须要有request和response两个参数。

  • 并且该方法必须要返回一个HttpResponse对象

    • 可以返回形参response
    • 也可以返回自己写的三板斧
  • 执行属性跟process_request方法相反,按照配置文件中注册的顺序由下往上执行。

  • 如果中间件中没有process_response方法,则跳过,到下一个中间中执行。

当一个中间件中既有process_request方法又有process_response方法,如果在该中间件的process_request方法中返回了一个HttpResponse对象,那么响应走的时候直接走同级的process_response返回。

其他三个方法(了解)

  • process_view

    由匹配成功之后执行视图函数之前,会自动执行中间件里面的该方法,顺序是按照配置文件中注册的中间件从上往下的顺序依次执行。

  • process_exception

    当视图函数中出现异常的情况下触发,顺序是按照配置文件中注册了的中间件从下往上依次经过。

  • process_template_response

    返回的HttpResponse对象有render属性的时候才会触发,顺序是按照配置文件中注册了的中间件从下往上依次执行。

关于执行顺序其实可以将五个方法分为两类:

  • 接收请求相关的函数:process_request/process_view

    请求是由外往django后端内走:所以执行的顺序就是由上往下,可以理解为由外往内走

  • 回复响应相关的函数:process_response/process_exception/process_template_response

    响应是由django后端内部往外回复给客服端:所以执行执行顺序是由下往上走,可以理解为由内往外走。

csrf跨站请求伪造

首先对钓鱼网址做个简单的了解

钓鱼网址:

以一个中国银行的钓鱼网址举例

假设我们搭建一个跟正规网站一模一样的界面(中国银行),用户不小心进到了我们的网址(钓鱼网址);当用户给某某人打钱的时候,这个操作确实提交到了中国银行的系统,用户的钱确确实实是减少了,但是唯一不同的是打钱的目标账号不是用户想要的打的账户变成了一个未知的账号,钱打到了 未知的账号上。

钓鱼网址内部本质:

钓鱼网站的页面针对目标账户只给用户提供一个没有name属性的普通input框,然后在内部自己隐藏一个已经写好name和value的input框,操作只会对该隐藏input框的value对应的账户生效。

假设我们是中国银行系统的开发者,又该怎么规避这种不正规的请求呢?

csrf跨站请求伪造校验

网站给服务端返回一个提交请求的页面时,给该页面添加一个随机的唯一标识,当该页面朝网址后端提交post请求的时候,后端先获取该标识进行比对,比对上了才进行正常的操作,如果比对不上直接将请求拒绝掉(403 Forbidden)。

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

简单模拟:

# 路由:
# 3.中国银行(端口号8000)
url(r'^china_bank/',views.china_bank),
# 4.钓鱼网址(端口号8001)
url(r'^phising/',views.phising)

# 视图函数:
def china_bank(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        target_account = request.POST.get('target_account')
        transfer_money = request.POST.get('money')
        print(f'{username}给{target_account}转了{transfer_money}元')
    return render(request, 'china_bank.html')


def phising(request):
    return render(request, 'phising.html')

页面:

{#中国银行网页#}
<body>
<h1 class="text-center" style="color:green;">中国银行官网</h1>
<form action="/china_bank/" method="post">
    <p>username:<input type="text" class="form-control" name="username"></p>
    <p>target_account:<input type="text" class="form-control" name="target_account"></p>
    <p>money:<input type="text" class="form-control" name="money"></p>
    <p><input type="submit" value="转账" class="btn btn-success btn-block"></p>
</form>
</body>


{#钓鱼网站页面#}
<body>
 <h1 class="text-center" style="color:red;">中国印行官网</h1>
<form action="/china_bank/" method="post">
    <p>username:<input type="text" class="form-control" name="username"></p>
    <p>target_account:<input type="text" class="form-control"></p>
    <p>money:<input type="text" class="form-control" name="money"></p>
    <p><input type="submit" value="转账" class="btn btn-success btn-block"></p>
    <input type="hidden" name="target_account" value="jason">
</form>
</body>

如果通过 csrf校验

之前我们用form表单提交post请求的时候都是选择将csrf中间件给注释掉,现在为了安全性考虑我们将不注释掉该中间件让它对post请求进行校验,那我们自己如何才能让网页通过校验呢?

form表单如果通过校验

利用模版语法在form表单里添加csrf_token

<h1 class="text-center" style="color:green;">中国银行官网</h1>
<form action="/china_bank/" method="post">
    {% csrf_token %} 
</form>

这样即使我们不注释中间件,中国银行网址也能向django提交post请求,而钓鱼网站的post请求就会被直接拒绝,即使钓鱼网址后端也给他的页面添加csrf_token也会被拒绝,因为该标识不是中国银行后端发出的。

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

ajax如何通过校验

  • 方式一:

    利用标签查找页面上的随机字符串,属性查找。

    data:{"username":"jason","csrfmiddlewaretoken":$('[name=csrfmiddlewaretoken]').val()},
    
  • 方式二:

    利用模版语法提供的快捷书写

    data:{"username":"jason","csrfmiddlewaretoken":'{{ csrf_token }}'},
    
  • 方式三:

    将下面代码复制到static静态文件夹内的一个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);
        }
      }
    });
    

    使用时将该js文件导入到自己的html页面即可。

    data:{"username":'jason'}
    

csrf相关装饰器

  • csrf_protect装饰器:修饰视图函数,在post请求来时候必须校验
  • csrf_exempt装饰器:修饰视图函数,在post请求来时候忽略校验

FBV

对于FBV来说,跟普通的装饰器的使用相同,直接放在函数上面即可。

需求: 1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验

from django.views.decorators.csrf import csrf_protect,csrf_exempt


# 需求一:
# 首先注释掉配置文件中的csrf
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
	#'django.middleware.csrf.CsrfViewMiddleware',
]
    
@csrf_protect    
def transfer(request):  # 少数需要校验的视图函数添加上装饰器
    pass


# 需求二:
# 打开配置文件中的csrf
MIDDLEWARE = [
	'django.middleware.csrf.CsrfViewMiddleware',
]

@csrf_protect    
def transfer(request):  # 给少数不需要校验的视图函数添加上装饰器
    pass

CBV

给CBV添加csrf_protect装饰器,三种给CBV添加装饰器的方法都有效。

from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View


# @method_decorator(csrf_protect,name='post')  # 第二种方式  有效
class MyCsrfToken(View):
    @method_decorator(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)  # 第一种方式  有效
    def post(self,request):
        return HttpResponse('post')

给CBV添加csrf_protect装饰器,只有第三种方式有效。

# @method_decorator(csrf_exempt,name='post')  # 第二种方式  无效
# @method_decorator(csrf_exempt,name='dispatch')  # 第三种方式  有效
class MyCsrfToken(View):
    # @method_decorator(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)  # 第一种方式  无效
    def post(self,request):
        return HttpResponse('post')

一种重要的编程思路:

补充知识点

importlib模块可以通过字符串导入字符串对应的模块,内部运用了反射。

import importlib

res = 'time'
ret=importlib.import_module(res)
print('start')
ret.sleep(3)
print('end')

我们通过学习中间件和配置文件中间的注册方法,发现只要注释掉配置文件内中间间的注册代码,就能实现让该中间件失效,也就是说可以通过修改配置文件就能实现功能的控制,这非常好用,于是我们简单的模拟下。

需求:实现qq、微信、短信同时发送一条相同的消息。

首先我们创建一个用于启动的start.py文件、一个存放配置的settings.py文件,和一个用来存放功能模块的包文件夹notify

day70——django中间件/自定义、csrf跨网址请求伪造、django一重要编程思路模拟

各个文件内代码

# settings
NOTIFY_LIST = [
    'notify.email.Email',
    'notify.qq.QQ',
    'notify.wechat.Wechat',
]

#__init__.py   核心原理
import settings
import importlib


def send_all(content):
    for i in settings.NOTIFY_LIST:
        model_path, class_name = i.rsplit('.', maxsplit=1)  # notify.email   Email'
        model = importlib.import_module(model_path)
        cls = getattr(model, class_name)
        obj = cls()
        obj.send(content)
        
# 模块文件之一:email.py (其他类似)       
class Email():
    def __init__(self):  # 发送邮件前期准备
        pass

    def send(self,content):
        print(f'邮箱发来消息:{content}')
               
# start.py
if __name__ == '__main__':
    import notify

notify.send_all('为什么不接电话!')

运行start文件,发送消息:

"""
邮箱发来消息:为什么不接电话!
qq发来消息:为什么不接电话!
微信发来消息:为什么不接电话!"""


# 如果需要将邮箱功能取消掉,只需要在配置文件中注释路径即可
NOTIFY_LIST = [
    #'notify.email.Email',
    'notify.qq.QQ',
    'notify.wechat.Wechat',
]
"""
qq发来消息:为什么不接电话!
微信发来消息:为什么不接电话!"""

以上这种编程思路运用到了面向对象、反射、鸭子类型、字符串导模块、包等方法,各功能之间的解耦合度很高,后期扩展功能只需添加新的功能模块然后在配置文件中注册即可,对其他功能没有任何影响。