django12--中间件、csrf、"插拔式"编程思想

1 django中间件

1.1 中间件介绍

# django中间件是django的门户
  1.请求来的时候需要先经过中间件才能到达真正的django后端
  2.响应走的时候最后也需要经过中间件才能发送出去


# 什么时候使用django中间件
  当我们需要给web后端添加一些全局相关的功能时可以使用中间件
  # eg:
    1.全局用户身份校验  每个用户的登录状态
    2.全局用户权限校验
    3.全局访问频率校验  每个用户的访问频率
    4.用户黑名单、白名单
        
        
# 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',
]


# 研究django中间件代码规律:
  每个中间件基本都有process_request和process_response方法
    
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
    process_response
    
  2.了解即可
    process_view
    process_template_response
    process_exception

1.2 自定义中间件

# 步骤:
1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内需要定义类 (这个类必须继承MiddlewareMixin)
  from django.utils.deprecation import MiddlewareMixin
    
  然后在这个类里,就可以自定义五个方法了
  (这五个方法并不是全部都需要书写,用几个写几个)
    
4.需要将类的路径以字符串的形式,注册到配置文件中才能生效
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',
    '你自己写的中间件的路径1',
    'app01.mymiddleware.MyMdd1',
    '你自己写的中间件的路径3',
]


# 中间件类的五个方法
# 1.必须掌握
  # process_request 
    1.请求来的时候需要经过每一个中间件里面的process_request方法
      该方法有1个参数request
      执行顺序是按照配置文件中注册的中间件从上往下依次执行
			
    2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
			
    3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
      而是直接原路返回  (eg: 校验失败不允许访问...)
        
    # 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属性的时候 才会触发
    执行顺序是按照配置文件中注册了的中间件从下往上

  # process_exception
    当视图函数中出现异常的情况下触发
    执行顺序是按照配置文件中注册了的中间件从下往上

2 csrf跨站请求伪造

2.1 csrf介绍

# csrf:Cross-site request forgery
  跨站请求伪造校验
    
  网站在给用户返回一个具有提交数据功能页面的时候 
  会给这个页面加一个唯一标识
  当这个页面朝后端发送post请求的时候
  后端会先校验唯一标识,如果唯一标识不对直接拒绝 (403 forbbiden)
  如果成功则正常执行


# csrf由来:
  钓鱼网站
    我搭建一个跟正规网站一模一样的界面 (eg:中国银行)
    用户不小心进入到了我们的网站,用户给某个人打钱
    打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
    但是唯一不同的时候打钱的账户不是用户想要打的账户,而变成了一个莫名其妙的账户

  内部本质
    我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
    然后我们在内部隐藏一个已经写好name和value的input框

  如何规避上述问题
    csrf跨站请求伪造校验


# Django 会在post请求时,进行csrf跨站请求伪造校验
  默认通过中间件 django.middleware.csrf.CsrfViewMiddleware


# CsrfViewMiddleware中间件的校验原理:
1.Django后端响应页面时,会给前端响应 带有 名为"csrftoken"的cookie


2.前端发送数据时,后端会从请求的两个地方获取   两者都 没有或校验错误,才会报crsf校验错误
  2.1 从请求的数据中,判断是否含有 名为'csrfmiddlewaretoken'的值
  2.2 或从请求的请求头中,判断是否含有 名为 "X-CSRFToken"的请求头


3.后端会分别对 该两个随机字符串(加密方式相似但不同),通过salt进行解密出唯一的secret   
  3.1 cookie中的"csrftoken"  64位随机字符串 (32位salt+32位密文)
  3.2 提交的'csrfmiddlewaretoken' token 64位随机字符串 (32位salt+32位密文)

  是否能解出同样的secret,若secret一样则本次请求合法。

2.2 如何符合校验

2.2.1 form表单符合校验

# form表单如何符合校验
  模板语法:{% csrf_token %}

# eg:    
<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>

2.2.2 ajax符合校验

前端 获取 csrf_token值

### 1.若是前后端混合项目,csrfmiddlewaretoken值 由Django生成,可采用 第一、二种方式

# 第一种: 利用jQuery的标签查找 获取页面上的随机字符串,并放进post的data数据里
  data: {
    "username":'jason',
    'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()
  }    
    
    
# 第二种: 利用模版语法提供的快捷书写
  data:{
    "username":'jason',
    'csrfmiddlewaretoken':'{{ csrf_token }}'
  }


### 2.若是前后端分类项目,前端通过请求,获取Django生成的csrfmiddlewaretoken值     
# 第三种: 通过前端Ajax请求,获取django生成的 csrfmiddlewaretoken值

$.ajax({
    url: "/get_csrf_token/",
    success:function (res) {
        console.log('csrfmiddlewaretoken':res)
        // 存储到本地,方便Ajax发生真正请求时,构造csrf_token的键值对
        localStorage.setItem("csrf_token", res)
    }
})


from django.middleware.csrf import get_token
from django.http import JsonResponse

def get_csrf_token(request):
    csrf_token = get_token(request)
    return HttpResponse(csrf_token)

方式1:csrf_token值,放在前端 Ajax请求的数据中

# 针对不同数据类型,Ajax的csrf校验可使用方法    
  # 1.Ajax发普通键值对, contentType:'urlencoded'时:直接使用


  # 2.Ajax发文件时,注意: 数据是需要添加到 formdata对象中
  formDataobj.append('csrfmiddlewaretoken', '{{ csrf_token }}')


  # 3.Ajax发json时,只能采用方式2--放请求头中(可自己写,也可直接用方式2的js脚本)
  因为Ajax发json格式,需要前端将数据进行json序列化 JSON.stringify(data),
  若csrf校验数据放在data里面,Django后端无法识别了 
    (数据获取由 request.POST 变成了 request.body)


# 先请求获取csrf_token值,再放进请求头中发生真正的数据请求
$.ajax({
    url: "/login/",
    method: "post",
    headers:{"X-CSRFToken":localStorage.getItem("csrf_token")},
    
    data: {
        user:"admin",
        pwd:"123"
    },
    success:function (res) {
        console.log(res)
    }
})

方式2:csrf_token值,放在前端 Ajax请求的请求头中

// 请求的数据,就不需要包含 "csrfmiddlewaretoken"的键值对,放原本的数据即可
data:{
    "username":'jason',
  }


// csrf.js: 通用方式     拷贝下面js代码在script中

// 1.从浏览器中的cookie中,获取名为'csrftoken'的cookie值
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');


// 2.jquery的ajax请求中封装了一个方法:ajaxSetup,可以为所有的ajax请求做一个集体配置
// 为所有ajax请求,都添加了"X-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);
    }
  }
});

2.3 csrf相关装饰器

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

# csrf_protect  需要校验
  针对csrf_protect,CBV符合装饰器的三种玩法
    
# csrf_exempt   忽视校验
  针对csrf_exempt,CBV只能给dispatch方法加才有效

    
    
# FBV: 函数正常加装饰器的形式


# @csrf_exempt
# @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: 三种都可以
  csrf_exempt : 只能加在dispatch方法上

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

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')  # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post')  # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
    # @method_decorator(csrf_protect)  # 针对csrf_protect 第三种方式可以
    # @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_protect)  # 针对csrf_protect 第一种方式可以
    # @method_decorator(csrf_exempt)  # 针对csrf_exempt 第一种方式不可以
    def post(self,request):
        return HttpResponse('post')

3 基于中间件的'插拔式'编程思想

# 中间件:添加字符串 进行功能注册,注释即功能关闭
  '包.模块.类'
    
  利用 字符串导入模块 + 反射机制  


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)

补充:字符串导入模块

# 模块:importlib
import importlib

res = 'myfile.b'

# 可以将字符串,按照模块来导入  但注意:该方法最小只能到py文件名 (即模块)
ret = importlib.import_module(res)  # from myfile import b

print(ret)
posted @ 2021-09-17 00:40  Edmond辉仔  阅读(64)  评论(0编辑  收藏  举报