Django中间件

Django中间件

一、Django中间件简介

​ django中间件是类似于是django的保安,请求的时候需要先经过中间件才能到达django后端(urls,views,templates,models),响应走的时候也需要经过中间件才能到达web服务网关接口

Django请求生命周期

Django请求生命周期

缓存数据库:当请求经过第一个中间件的时候,Django会去缓存数据库看看,当前请求资源是不是已经存在于缓存数据库,如果存在那么直接从缓存数据库中将资源拿出来返回给浏览器,就不走Django后端了,减轻了Django后端和数据库的压力;如果没有那么继续向Django后端请求,等拿到资源,向浏览器发送响应的时候,走到最后一个中间件时,会将拿到的资源在缓存数据库中存一份,然后再讲响应发送给浏览器。

Django默认中间件有七个

Django默认中间件

这些字符串就是路径,我们可以通过模块的方式导入

SecurityMiddleware则是一个类

同理,SessionMiddleware等等也都是一个类

观察这些类,发现这些类都继承了MiddlewareMixin,并且都有process_request方法

Django中间件中有五个用户可以自定义的方法,需要我们掌握的方法有process_request()方法,process_response()方法,需要了解的方法有process_view()process_exception()process_template_response()

Django中间件可以用来做什么

​ 1、网站全局的身份校验,访问频率限制,权限校验...只要是涉及到全局的校验都可以在中间件中完成

​ 2、django的中间件是所有web框架中做的最好的

二、Django中间件需要掌握的两个方法

​ 首先我们需要自定义我们自己的中间件,在全局建一个文件夹,再建一个py文件,在py文件中写我们自己的中间件

模仿Django源码的写法,我们写了三个自定义中间件,并在其中写了process_request方法

然后我们需要去settings配置文件中注册我们写的中间件

我们自定义的中间件在书写路径时是没有提示的,所以一定注意不能写错

然后我们去写一个视图函数

启动Django,在浏览器中访问该url

观察终端打印的结果,发现中间件是在视图函数执行之前执行的,并且中间件的执行是有顺序的,按照在配置文件中书写的顺序从上往下执行

然后我们在三个自定义中间件中定义process_response方法

其余两个中间件类似(一定要返回response对象),定义完成之后,启动Django,访问url,得到如下结果

可以看出process_response方法是在执行完视图函数之后执行的,并且process_response执行顺序与process_request相反,是按照settings配置文件中书写顺序从下往上执行的

如果在第一个中间件返回一个HTTPResponse对象,会发生什么事呢?

如果方法里面直接返回了HttpResponse对象,那么会直接返回不再往下执行,基于该特点就可以做访问频率限制,身份校验,权限校验

而如果是flask框架,就不是在同级别的中间件返回,而是从最下面的中间件开始返回,依次执行process_response方法

在process_response方法中,必须要将response对象返回,因为它指代的就是要返回给前端的数据

三、Django中间件中需要了解的三个方法

1、process_view方法

先在三个中间件中定义peocess_view方法

启动Django,访问url

process_view方法是在执行视图函数之前执行,并且是在路由匹配成功后

2、process_exception方法

同样的定义process_exception方法

访问url

发现process_exception并没有执行

当我们在视图函数中乱写两行之后

浏览器报错,并且执行了process_exception方法

总结:process_exception会在视图函数报错的时候执行

3、process_template_response方法

仍然在中间件中定义process_template_response方法

注意到这里也返回了response对象,实际上只要形参有response就必须返回response,不然的话就相当于你借了别人东西却不还一样,会报错。

并且将视图改为

浏览器展示的内容是render属性加括号调用的结果

总结:返回的对象中必须带有render属性才会执行

四、csrf跨站请求伪造

1、钓鱼网站

钓鱼网站:通过制作一个跟正儿八经的网站一模一样的页面,骗取用户输入信息,转账交易,从而做手脚,转账交易的请求确确实实是发给了中国银行,账户的钱也是确确实实少了,唯一不一样的地方在于收款人账户不对。
内部原理:在让用户输入对方账户的那个input上面做手脚,给这个input不设置name属性,在内部隐藏一个实现写好的name和value属性的input框,这个value的值就是钓鱼网站受益人账号。

2、自己实现钓鱼网站

3、防止钓鱼网站的思路

​ 网站会给返回给用户的form表单页面偷偷塞一个随机字符串,请求到来的时候会先比对随机字符串是否一致,如果不一致直接拒绝(403)

该随机字符串有以下特点:同一个浏览器每一次访问都不一样,不同浏览器绝对不会重复

4、防止钓鱼网站

真正网站的form表单中只有三个p标签和一个input框

而如果在表单中塞一个{% csrf_token %}

​ 真正的网站的form表单中就会多出一个隐藏的input框 。name属性标识它是一个csrf秘钥,value值是一个随机字符串,并且每发送一次请求这个随机字符串都是不一样的,不同的浏览器上的随机字符串绝对不会重复

5、发送ajax请求时如何防止钓鱼网站

1.现在页面上写{% csrf_token %},利用标签查找  获取到该input键值信息
{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()}
$('[name=csrfmiddlewaretoken]').val()
				
2.直接书写'{{ csrf_token }}'
	{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}
	{{ csrf_token }}
			
3.你可以将该获取随机键值对的方法 写到一个js文件中,之后只需要导入该文件即可
新建一个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);
        }
    }
});
// 当你写一个前后端分离的项目时,前端无法使用模板语法,就可以使用导入文件的方法

6、跨站请求伪造相关装饰器

当你把settings配置文件中间件中的csrf中间件打开时,所有向后端提交post请求的数据,都会被csrf校验,如果我们想让某一个或几个请求跳过校验该怎么办呢?当你网站全局不校验csrf的时候,有几个需要校验又该如何处理?

这时我们就需要用到csrf_exemptcsrf_protect,见名知意,csrf_exempt是用来跳过校验的,而csrf_protect是用来在不校验的情况下进行校验的

朝login发一个form表单的post请求,并没有带上csrf_token随机字符串,发现并没有校验

而如果把装饰器去掉就会被校验

同理csrf_protect的使用也是类似的

在CBV中又如何给视图函数加装饰器呢?

这么装行不行呢?

结果是不行的,那该怎么装呢?

# 如果是csrf_protect 那么有三种方式
	# 第一种方式
	# @method_decorator(csrf_protect,name='post')  # 有效的
    class MyView(View):
        # 第三种方式
        # @method_decorator(csrf_protect)
        def dispatch(self, request, *args, **kwargs):
            res = super().dispatch(request, *args, **kwargs)
            return res

        def get(self,request):
            return HttpResponse('get')
        # 第二种方式
        # @method_decorator(csrf_protect)  # 有效的
        def post(self,request):
            return HttpResponse('post')
					
# 如果是csrf_exempt 只有两种(只能给dispatch装)   特例
	@method_decorator(csrf_exempt,name='dispatch')  # 第二种可以不校验的方式
    class MyView(View):
        # @method_decorator(csrf_exempt)  # 第一种可以不校验的方式
        def dispatch(self, request, *args, **kwargs):
            res = super().dispatch(request, *args, **kwargs)
            return res

        def get(self,request):
            return HttpResponse('get')

        def post(self,request):
            return HttpResponse('post')

csrf_exempt只能给dispatch装,而csrf_protect既可以给dispatch装,也可以给post

装饰器中只有csrf_exempt是特例,其他的装饰器在给CBV装饰的时候都可以有三种方式

五、auth模块方法大全

1、auth模块有哪些功能?

auth模块集成了和用户相关的功能,例如用户的注册,登录,验证,修改密码等等...

auth_user表中的password字段是加密的,加密方式是sha256

Django后台管理登录界面

执行数据库迁移命令之后,会生成很多表,其中的auth_user是一张用户相关的表格(包含了超级用户即管理员和普通用户,利用is_superuser来区分)

在run manage by task中输入createsuperuser 即可创建超级用户,这个超级用户就拥有登陆django admin后台管理的权限

在run manage by task中创建超级用户可以不输入邮箱,它只会给你一个提示

2、查询用户

from django.contrib import auth
user_obj = auth.authenticate(username=username,password=password)  
# 必须要用这种方式因为数据库中的密码字段是密文的 而你获取的用户输入的是明文

3、记录用户状态

auth.login(request,user_obj)  # 将用户登录状态记录到django_session表中
# 只要执行了这一句话,你就可以在后端任意位置通过request.user获取到当前用户对象
# 如果没有执行这一句话,就通过request.user获取用户对象,那么拿到的是匿名用户AnonymousUser
# 并且request.user.password获取用户密码时会报错

这个随机字符串也会在你的浏览器存一份

4、判断用户是否登录

print(request.user.is_authenticated)  # 判断用户是否登录  如果是匿名用户会返回False

5、用户登录之后 获取用户对象

print(request.user)  # 如果没有执行auth.login那么拿到的是匿名用户

6、校验用户是否登录

from django.contrib.auth.decorators import login_required
# 如果用户没有登录,那么它会跳到一个莫名其妙的页面,这种情况肯定是不允许发生的,
# 所以我们必须指定它跳到我们写的登录页面
@login_required(login_url='/xxx/')  # 局部配置
def index(request):
    pass

# 全局配置  settings文件中 
# auth登录认证装饰器,跳转的URL
LOGIN_URL = '/xxx/'

7、验证密码是否正确

request.user.check_password(old_password)

8、修改密码

request.user.set_password(new_password)
request.user.save()  # 修改密码的时候 一定要save保存 否则无法生效

9、退出登陆

auth.logout(request)  # request.session.flush()

10、注册用户

from django.contrib.auth.models import User
# 上面这句话就可以拿到Django自动为我们创建的auth_user表
# User.objects.create(username =username,password=password)  # 创建用户名的时候 千万不要再使用create了,它会将密码以明文的形式存进表中
# User.objects.create_user(username =username,password=password)  # 创建普通用户
User.objects.create_superuser(username =username,password=password,email='123@qq.com')  # 创建超级用户  邮箱必填

注意事项:

如果你想用auth模块,那么你就用全套。

auth.authenticate(username=username)这么写无效的,该方法不能只传一个参数。

11、自定义用户表

Django自动帮我们创建用户表的字段是固定的,如果我们想在用户表中添加几个字段,就必须要自己自定义用户表。

思路:

1.使用一对一关系,新建一张表加入新增字段与auth_user表关联(繁琐)

2.使用类的继承,继承Django默认的用户表类,添加新的字段,已有的字段不需要书写,在默认表中就有,这种方式必须在同步数据库之前使用

然后还必须在settings配置文件中告诉Django,我们不再用你默认的那张表了

app01_userinfo表新增了两个字段,Django默认用户表的字段也全都有

并且我们自定义的表拥有auth模块的所有功能

六、模仿Django中间的思想用字符串添加功能

1、架构

2、start.py

import notify

notify.send_all('国庆放假了 记住放八天哦')

3、settings.py

NOTIFY_LIST = [
    'notify.email.Email',
    'notify.msg.Msg',
    # 'notify.wechat.WeChat',
    'notify.qq.QQ',
]

4、notify/email.py

class Email(object):
    def __init__(self):
        pass  # 发送邮件需要的代码配置

    def send(self,content):
        print('邮件通知:%s'%content)

5、notify/msg.py

class  Msg(object):
    def __init__(self):
        pass  # 发送短信需要的代码配置

    def send(self,content):
        print('短信通知:%s' % content)

6、notify/qq.py

class QQ(object):
    def __init__(self):
        pass  # 发送qq需要的代码准备

    def send(self,content):
        print('qq通知:%s'%content)

7、notify/wechat.py

class WeChat(object):
    def __init__(self):
        pass  # 发送微信需要的代码配置

    def send(self,content):
        print('微信通知:%s'%content)

8、notify/__init__.py

import settings
import importlib


def send_all(content):
    for path_str in settings.NOTIFY_LIST:  # 1.拿出一个个的字符串   'notify.email.Email'
        module_path,class_name = path_str.rsplit('.',maxsplit=1)  # 2.从右边开始 按照点切一个 ['notify.email','Email']
        module = importlib.import_module(module_path)  # from notity import msg,email,wechat
        cls = getattr(module,class_name)  # 利用反射 一切皆对象的思想 从文件中获取属性或者方法 cls = 一个个的类名
        obj = cls()  # 类实例化生成对象
        obj.send(content)  # 对象调方法
posted @ 2019-09-25 23:06  竣~  阅读(246)  评论(0编辑  收藏  举报