Django中间件
一、Django中间件简介
? django中间件是类似于是django的保安,请求的时候需要先经过中间件才能到达django后端(urls,views,templates,models),响应走的时候也需要经过中间件才能到达web服务网关接口。
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_exempt
和csrf_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='[email protected]') # 创建超级用户 邮箱必填
注意事项:
如果你想用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) # 对象调方法