Django中间件
一、什么是中间件
django中间件就是类似于django的保安;请求来的时候需要先经过中间件,才能到达django后端(url,views,models,templates),
响应走的的时候也需要经过中间件才能到达web服务器网关接口处;
中间件位于web服务端与url路由层之间;是介于request与response处理之间的一道处理过程。
二、中间件有什么用
如果你想修改请求,例如被传送到view中的HttpRequest对象。 或者你想修改view返回的HttpResponse对象,这些都可以通过中间件来实现。
可以用来做什么?
1、网站全局的身份校验,访问频率限制,权限检验等;只要涉及到全局校验的都可以用中间件来实现
2、Django的中间件是所有的web框架中做得最好的
Django默认的中间件:(在django项目的settings模块中,有一个MIDDLEWARE_CLASSES变量,其中的每一个元素就是一个中间件)
django默认的中间件有七个如下图:
三、自定义中间件
中间件可以定义五个方法;其中主要的是(process_request:请求 和process_response:返回)
1、process_request(self,request) 2、process_view(self, request, callback, callback_args, callback_kwargs) 3、process_template_response(self,request,response) 4、process_exception(self, request, exception) 5、process_response(self, request, response)
以上的方法的返回值可以是None或一个HttpResponse对象,如果是None,继续按照Django定义的规则向后继续执行,
如果是HttpResponse对象,则直接将该对象返回给用户即可。
1、process_request和process_response
当用户发起请求的时候会依次经过所有的的中间件,这个时候的请求时process_request,最后到达views的函数中,views函数处理后,在依次穿过中间件,这个时候是process_response,最后返回给请求者。
如下图所示,一个完整的中间件的流程:
2、Djang请求生命周期:
wsgi---中间件---路由---视图---中间件---wsgi--
通过完整Django的完整构造图,能够扩展结合Django每个知识点,深入了解每个知识点所涉及到的内容。
3、需要重点掌握的中间件方法:
1、.process_request()方法
规律:
1、请求来的时候,会经过每个中间件里面的process_request()方法(从上到下的顺序)
2、如果返回的是HttpResponse对象,那么会直接返回,不再往下执行了;基于这一特点就可以做访问的频率限制,身份校验,权限校验等
2、process_response()方法
规律:
(1)、必须将response形参返回,因为这个形参指代的就是要返回给前端的数据。文章地址https://www.yii666.com/article/764150.html
(2)、响应走的时候,会依次经过每一个中间件里面的process_response方法(从下往上)
需要了解的方法:
(1)、process_view() :
在路由匹配成功执行视图函数之前 触发
(2)、process_exception() :
当你的视图函数报错时 就会自动执行
(3)、process_template_response()
当你返回的HttpResponse对象中必须包含render属性才会触发
4、自定义的中间件,写的类必须继承 MiddlewareMixin
(1)第一步:导入
from django.utils.deprecation import MiddlewareMixin
(2)、自定义中间件,新建文件件书写
from django.utils.deprecation import MiddlewareMixin#
from django.shortcuts import HttpResponse
#
class Md1(MiddlewareMixin):
#
def process_request(self,request):
print("Md1请求")
#
def process_response(self,request,response):
print("Md1返回")
return response
#
class Md2(MiddlewareMixin):
#
def process_request(self,request):
print("Md2请求")
#return HttpResponse("Md2中断")
def process_response(self,request,response):#
print("Md2返回")
return response
(3):在views中定义一个视图视图函数(index)
def index(request): print("view函数...")
return HttpResponse("OK")
(4)、在settings.py的MIDDLEWARE里注册自己定义的中间件
1.如果你想让你写的中间件生效,就必须要先继承MiddlewareMixin
2.在注册自定义中间件的时候,一定要确保路径不要写错
(5)查看运行的结果:得出上面的总结规律
请求得出的规律:
返回得出的规律:
第二种情况,当自定义的中间件种有HttpRsponse时,直接返回:
文章来源地址:https://www.yii666.com/article/764150.html
(6)如果没有返回response形参,因为这个形参指代的就是要返回给前端的数据
报错结果显示:
(7)、其他方法了解:
1、process_view
该方法有四个参数
process_view(self, request, view_func, view_args, view_kwargs)
实例:
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import HttpResponse,redirect,render,reverse class Md1(MiddlewareMixin):
def process_request(self,request):
print('Md1请求')
return HttpResponse("Md1中断") def process_response(self,request,response):
print('Md1返回')
return response
# return HttpResponse("嘿嘿!") def process_view(self,request,callback,callback_args,callback_kwargs):
print('Md1views') class Md2(MiddlewareMixin):
def process_request(self,request):
print("Md2请求")
return HttpResponse('Md2中断') def process_response(self,request,response):
print('Md2返回')
return response def process_view(self,callback,callback_args,callback_kwargs):
print("Md2views")
2、process_exception,该方法两个参数:
process_exception(self, request, exception)
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
3、process_template_response(self,request,response)方法:
该方法对视图函数返回值有要求,必须是一个含有render方法类的对象,才会执行此方法
总结:你在书写中间件的时候 只要形参中有repsonse 你就顺手将其返回 这个reponse就是要给前端的消息
二、CSRF_TOKEN跨站请求伪造
1、什么是csrf
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding;
简单的理解:就是攻击者盗用了你的身份,以你的名义发送恶意的请求,对服务器来说这个请求是完全合法的;
要完成一次CSRF攻击,受害者必须依次完成两个步骤:
1.登录受信任网站A,并在本地生成Cookie。
2.在不登出A的情况下,访问危险网站B。
简单的举例钓鱼网站:开两个django项目,模拟转账的现象
正规的网站:
views.py
def transfer(request):
if request.method == 'POST':
username = request.POST.get('username')
money = request.POST.get('money')
target_user = request.POST.get('target_user')
print('%s 给 %s 转了 %s元'%(username,target_user,money))
return render(request,'res.html')
res.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<h2>这是正儿八经的网站</h2>
<form action="//" method="post">
{# {% csrf_token %}#}
<p>本人用户名:<input type="text" name="username"></p>
<p>转账金额:<input type="text" name="money"></p>
<p>对方账户:<input type="text" name="target_user"></p>
<input type="submit">
</form>
钓鱼网站破解原理:
在让用户输入对方账户的那个input上面做手脚,写一个一样的viewx.py,和路由url,
修改前端HTML的内容让转账对方的用户隐藏起来绑定value=’jason‘,启动时修改端口。
防止钓鱼网站的思路:
网站会给返回的用户的form表单页面,偷偷的噻一个随机的字符串,请求来的时候,
会先比对随机字符串是否一致,如果不一致,直接拒绝(403)
该随机字符串有一下特点:
1、同一个浏览器没一次访问都不一样
2、不同的浏览器之间绝对不会重复
跨站请求伪造的解决方法:
1、form表发送post请求的时候,只需要书写一句话即可
{% csrf_token %}
书写{% csrf_token %},会在客户端生成一对键值对
2、用AJAX发送post请求时,如何避免csrf校验
(1)、现在页面上写{% csrf_token %},利用标签查找 ,获取到该input键值信息,关键字:'csrfmiddlewaretoken'
{'username':'jason','csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()}
(2)、直接书写'{{ csrf_token }}'
{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}
(3)、你可以将该获取随机键值对的方法,写到一个js文件中,之后只需要导入该文件即可使用。
添加static到settings.Py中:
然后在使用的前端html页面导入:
书写静态文件存放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);
}
}
});
三、跨站请求伪造相关的装饰器
1.当你网站全局都需要校验csrf的时候 有几个不需要校验该如何处理?
2.当你网站全局不校验csrf的时候 有几个需要校验又该如何处理?
全站禁用:注释掉中间件 'django.middleware.csrf.CsrfViewMiddleware',
局部禁用:用装饰器(在FBV中使用)
在CBV中使用:
CBV比较特殊,不能单独加在某个方法上;只能加在类上或dispatch方法上
from django.test import TestCase # Create your tests here.
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt, 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')
Auth认证模块
执行数据库迁移的那两条命令时,即使我们没有建表,django是不是也会创建好多张表?
我们创建之后去看一下里面的一个叫auth_user表,这个表跟用户的相关,既然是表,那肯定应该有对应的操作改表的方法
auth跟用户相关的功能模块:用户的注册、登录、验证、修改密码等。
首先创建超级用户:createsuperuser,这个超级用户就可以拥有登陆django admin后台管理的权限
在创建超级用户时:不可手动插入,因为密码事加密的
这个超级用户可以登录到Django后台管理权限 :
基于这张表写一个登录的功能:
如果想用auth模块,那就必须用全套,比如用户的获取和保存等auth.authenticate,后期就不能用session来保存
from django.contrib import auth # 必须要用 因为数据库中的密码字段是密文的 而你获取的用户输入的是明文
查询用户,比较数据
user_obj = auth.authenticate(username=username,password=password)
记录用户状态
auth.login(request,user_obj) # 将用户状态记录到session中 判断用户是否登录,用了auth。login 后就可以用.属性获取
print(request.user.is_authenticated) # 判断用户是否登录,如果是你们用户会返回False 用户登录之后 获取用户对象
print(request.user) # 如果没有执行auth.login那么拿到的是匿名用户 校验用户是否登录
from django.contrib.auth.decorators import login_required
@login_required(login_url='/xxx/') # 局部配置
def index(request):
pass
修改成全局配置 放在settings文件中,LOGIN URL = "/XXX/"
方法在用户注册登录的简单校验的实际运用:
from django.contrib import auth
def xxx(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
# 取数据库查询当前用户数据
# models.User.objects.filter(username=username,password=password).first()
# 必须要用 因为数据库中的密码字段是密文的 而你获取的用户输入的是明文
user_obj = auth.authenticate(username=username,password=password)
print(user_obj)
# print(user_obj)
# print(user_obj.username)
# print(user_obj.password)
# 保存用户状态
# request.session['user'] = user_obj
auth.login(request,user_obj) # 将用户状态记录到session中
"""只要执行了这一句话 你就可以在后端任意位置通过request.user获取到当前用户对象"""
return render(request,'xxx.html') def yyy(request):
print(request.user) # 如果没有执行auth.login那么拿到的是匿名用户
print(request.user.is_authenticated) # 判断用户是否登录 如果是你们用户会返回False
# print(request.user.username)
# print(request.user.password)
return HttpResponse('yyy')
修改密码、退出登录、注册用户等功能
装饰器校验是否登陆及跳转
from django.contrib.auth.decorators import login_required @login_required(login_url='/login/',redirect_field_name='old')
# 没登陆会跳转到login页面,并且后面会拼接上你上一次想访问的页面路径/login/?next=/test/,可以通过参数修改next键名
def my_view(request):
pass
如果我所有的视图函数都需要装饰并跳转到login页面,那么我需要写好多份
# 可以在配置文件中指定auth校验登陆不合法统一跳转到某个路径
LOGIN_URL = '/login/' # 既可以局部配置,也可以全局配置
回到最上面,我们是怎么对auth_user表添加数据的?命令行输入~~~合理不?
from django.contrib.auth.models import User
def register(request):
User.objects.create() # 不能用这个,因为密码是明文
User.objects.createuser() # 创建普通用户
User.objects.createsuperuser() # 创建超级用户
校验密码,修改密码
request.user.check_password(pwd) # 为什么不直接获取查,因为前端用户输入的是明文数据库密文 request.user.set_password(pwd)
request.user.save() # 修改密码
自带的登录装饰器:
from django.contrib.auth.decorators import login_required
具体的使用
from django.contrib.auth.decorators import login_required # 修改用户密码
@login_required # 自动校验当前用户是否登录 如果没有登录 默认跳转到 一个莫名其妙的登陆页面
def set_password(request):
if request.method == 'POST':
old_password = request.POST.get('old_password')
new_password = request.POST.get('new_password')
# 先判断原密码是否正确
# 将获取的用户密码 自动加密 然后去数据库中对比当前用户的密码是否一致
is_right = request.user.check_password(old_password)
if is_right:
print(is_right)
# 修改密码
request.user.set_password(new_password)
request.user.save() # 修改密码的时候 一定要save保存 否则无法生效
return render(request,'set_password.html')
注销用户:
@login_required(/login/url='xxx'/)
def logout(request):
# request.session.flush()
auth.logout(request) 退出
return HttpResponse("logout") from django.contrib.auth.models import User
def register(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = User.objects.filter(username=username)
if not user_obj:
# 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') # 创建超级用户
return render(request,'register.html')
自定义模型表应用auth功能
如何扩张一张表auth_user表呢?
一对一的关联(不推荐使用)
from django.contrib.auth.model import User class UserDetail(models.Models):
phone = models.CharField(max_length=11)
user = models.OnoToOneField(to=User)
面向对象的继承
from django.contrib.auth.models import User,AbstractUser
class UserInfo(AbstractUser):
phone = models.CharField(max_length=32) # 需要在配置文件中,指定我不再使用默认的auth_user表而是使用我自己创建的Userinfo表
AUTH_USER_MODEL = "app名.models里面对应的模型表名" """
自定义认证系统默认使用的数据表之后,我们就可以像使用默认的auth_user表那样使用我们的UserInfo表了。
库里面也没有auth_user表了,原来auth表的操作方法,现在全部用自定义的表均可实现
"""
使用的userinfo表需要在settings中配置,告诉Django不在自动创user表了,换成user info表
以后在使用时,创建的用户表可以添加除了auth_user自带的字段外,只需要添加额外的字段名。
新创建的django的user表都会自带以下一些字段:
如以下BBS项目中models.py中user表的创建
数据库迁移命令执行后会额外的添加字段:
基于Django 中间件思想,实现可插拔功能
简单的说就是,当在settings中把中间件某些相关的功能注释掉之后,某些功能就会失效,打开又可以使用
按照settings源码结构分析,推导得出相应的结论,基于这结论之上,写一些自定义的中间件:
举例开发一个实现集体通讯发信息,如用短信、微信、QQ发
分析:
根据不同功能写不同的文件,秉承python的编成思想用鸭子类型,面向对象式编程写成类定义相同的def方法,
然后再继承同样的send_all,再创建一个settings把所有单独的功能添加配置。
新建一个文件:包含三个方法:email、msg、wechat __init__
email.py
class Email(object):
def __init__(self):
pass
def send(self,content):
print('邮件通知:%s'%content)
msg.py
class Msg(object):
def __init__(self):
pass
def send(self,content):
print('短信通知:%s'%content)
wechat.py
class WeChat(object):
def __init__(self):
pass
def send(self,content):
print('微信通知:%s'%content)
核心部分的代码 __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) # 对象调方法
settings.py网址:yii666.com
NOTIFY_LIST = [
'notify.email.Email',
'notify.msg.Msg',
'notify.wechat.WeChat',
# 'notify.qq.QQ',
]
start.py
import notify notify.send_all('国庆放假了,学习使我快乐!')
把QQ的注释掉执行的的结果如下:
---恢复内容结束---