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='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) # 对象调方法