django请求生命周期:
django中间件称之为django的门户也有人叫它保安,由于当Django处理一个 Request(请求) 的过程是首先通过中间件,然后再通过默认的 URL 方式进行的。我们可以在 Middleware 这个地方把所有Request 拦截住,用我们自己的方式完成处理以后直接返回 Response。因此了解中间件的构成是非常必要的。
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
在django项目的settings模块中,有一个 MIDDLEWARE_CLASSES 变量,其中每一个元素就是一个中间件,如下图:
1.请求来了首先走的是process_request方法,如果没有直接通过,可在其中直接返回,相当于浏览器传过来的请求格式不对,我们可以直接返回。(从前向后)
2.响应走的时候是走时候是走process_response方法,是从后向前的。
自定义中间件:
自定义中间件的五中方法:
- process_request(self,request)#
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
以上方法的返回值可以是None或一个HttpResponse对象,如果是None,则继续按照django定义的规则向后继续执行,如果是HttpResponse对象,则直接将该对象返回给用户
自定义中间件:
新建一个任意名字的文件夹,里面新建一个任意名字的文件夹
中间件本质就是含有5个可以修改的内置方法的类,所以自定义的时候需要做的就是先继承一个Django提供的中间件混合父类–MiddlewareMixin
from django.utils.deprecation import MiddlewareMixin
# MiddlewareMixin 所有中间件的父类。
from django.shortcuts import redirect,HttpResponse,render
class MyMiddleWare(MiddlewareMixin):
def process_request(self,request):
print("我是第一个process_request")
def process_response(self,request,response):
print("我是第一个process_response")
return response
def process_view(self,request,view_func,view_args,view_kwargs):
print("我是第一个process_view")
print(view_args,view_kwargs)
print(view_func.__name__)
# return HttpResponse("hal")
def process_exception(self,request,exception):
print("我是一个自定义的process_exception")
def process_template_response(self,request,response):
print("我是第1个自定义的中间件process_template_response")
return response
还需要去settings.py中注册两个中间件:
请求来的时候是依次往下走的因为是列表的缘故
请求走的时候也是从下往上依次执行,但是我们要知道的是,django中说你在当前这个中间件中的process_request中返回了HttpResponse的话,会直接走这个中间件的response方法,直接按照这个中间件的顺序从下往上返回信息。
执行结果如下图:
注意:如果你的自定义process_response不返回一个return response的话,会直接报错
报错信息是:
在自定义的中间件中process_response返回了response
process_request
process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。
由于request对象是一样的,所以我们可以对request对象进行一系列的操作,包括request.变量名=变量值,这样的操作,我们可以在后续的视图函数中通过相同的方式即可获取到我们在中间件中设置的值。
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
我们来看看多个中间件时,Django是如何执行其中的process_request方法的。
class Mymiddlewera(MiddlewareMixin):
def process_request(self,request):
print('我是app02中mymiddlewera中第一个自定义的中间件方法请求方法')
class Mymiddlewera2(MiddlewareMixin):
def process_request(self,request):
print('我是app02中mymiddlewera中第二个自定义的中间件方法请求方法')
settings中配置:
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',
'app02.mymiddlewera.middle.Mymiddlewera', # 自定义中间件1
'app02.mymiddlewera.middle.Mymiddlewera2' # 自定义中间件2
]
通过终端打印,我们发现中间件是自上而下依次执行
然后我们调换一下位置:
在打印一下两个自定义中间件中process_request方法中的request参数,会发现它们是同一个对象。
- 中间件的process_request方法是在执行视图函数之前执行的。
- 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
- 不同中间件之间传递的request都是同一个对象
process_response
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,request与response的关系相当于是栈的关系,先进后出,先走的request,最后返回response。
定义process_response方法时,必须给方法传入两个形参,request和response。request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象(也就是说这是Django后台处理完之后给出一个的一个具体的视图)。该方法的返回值(必须要有返回值)也必须是HttpResponse对象。如果不返回response而返回其他对象,则浏览器不会拿到Django后台给他的视图
通过我们上面的代码和图片演示,我们直接进行总结
总结:
process_response方法是在视图函数之后执行的,并且顺序是从下往上依次执行。
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
process_view
process_view(self, request, view_func, view_args, view_kwargs)
该方法有四个参数
request是HttpRequest对象。
view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表.
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
Django会在调用视图函数之前调用process_view方法。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法,最后返回给浏览器
class Mymiddlewera(MiddlewareMixin):
def process_request(self,request):
print('我是app02中mymiddlewera中第一个自定义的中间件方法请求方法')
print('111',request)
# return HttpResponse('好想好好的睡个一夜直到天亮')
def process_response(self,requset,response):
print('我是app02中mymiddlewera中第一个自定义的中间件方法响应方法')
return response # 必须将response接收到的数据返回,不然报错
def process_view(self,request,view_func,view_args,view_kwargs):
print('111,你说啥就是啥吧')
print(view_func)
print(view_args)
print(view_kwargs)
终端打印结果
process_view方法是在Django路由系统之后,视图系统之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
process_exception
process_exception(self, request, exception)
该方法两个参数:
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
class Mymiddlewera(MiddlewareMixin):
def process_request(self,request):
print('我是app02中mymiddlewera中第一个自定义的中间件方法请求方法')
print('111',request)
# return HttpResponse('好想好好的睡个一夜直到天亮')
def process_response(self,requset,response):
print('我是app02中mymiddlewera中第一个自定义的中间件方法响应方法')
return response # 必须将response接收到的数据返回,不然报错
def process_view(self,request,view_func,view_args,view_kwargs):
print('111,你说啥就是啥吧')
print(view_func)
print(view_args)
print(view_kwargs)
def process_exception(self,request,exception):
print('222,你可真烦人啊!')
print(exception)
process_template_response(用的比较少)
process_template_response(self, request, response)
它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
class Mymiddlewera(MiddlewareMixin):
def process_request(self,request):
print('我是app02中mymiddlewera中第一个自定义的中间件方法请求方法')
print('111',request)
# return HttpResponse('好想好好的睡个一夜直到天亮')
def process_response(self,requset,response):
print('我是app02中mymiddlewera中第一个自定义的中间件方法响应方法')
return response # 必须将response接收到的数据返回,不然报错
def process_view(self,request,view_func,view_args,view_kwargs):
print('111,你说啥就是啥吧')
print(view_func)
print(view_args)
print(view_kwargs)
def process_exception(self,request,exception):
print('222,你可真烦人啊!')
print(exception)
def process_template_response(self,request,response):
print('我是app02中的mymiddlewera中第一个自定义的render方法')
return response
def index(request):
print('我是index,哈哈哈哈')
# print(request.method)
def render():
print('567890')
# return 123
return HttpResponse('haodezhidaole ')
obj = HttpResponse('ok')
# print(1111)
obj.render=render # 这一步实际上触发了process
return obj
视图函数执行完之后,立即执行了中间件的process_template_response方法,顺序是倒序,先执行MD1的,在执行MD2的,接着执行了视图函数返回的HttpResponse对象的render方法,返回了一个新的HttpResponse对象,接着执行中间件的process_response方法。
总结:
process_request:请求来的时候从下往上依次执行每一个中间件里面的process_request
process_response:响应走的时候会从下往上依次执行执行每一个中间件的process_response方法
process_view:路由匹配成功,执行视图函数之前自动触发(顺序是从上往下依次执行),view_func是执行视图函数的名字
process_exception:当视图函数出现报错,会自动触发,顺序是依次往下往上执行
process_template_response:当你返回对象的时候有一个render()方法的时候会触发,执行顺序从下往上依次执行
django中间件能够帮我实现 网站全局的身份验证,黑名单,白名单,访问频率限制,反爬相关等等
》》》:django用来帮你做全局相关的功能校验
RBAC:基于角色的权限管理(不同的客户可以给出不同的访问权限)
所以后面只要是涉及到了全局相关,我们就可以直接再中间件这里进行操作,比如说去数据库中进行校验这个用户权限,然后只对他开放特定的功能块儿!
跨站请求伪造(CSRF)
钓鱼网站就是建一个和正常的网站一模一样的网站,然后用户在输入的时候调的也是正常网站的接口去处理,所以用户的钱会扣掉,但是并没有转给指定的人,其实就是建了一个和正常网站一模一样的东西,然后偷偷的在转给目标用户那里,偷偷的将input框当前的name去掉,然后用了一个hidden隐藏起来,在隐藏起来的input框中给一个默认的value,具体示例如下
就是偷换你转账账户的name,与value,从而达到欺骗别人
<!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>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<h1>钓鱼网站</h1>
<form action="http://127.0.0.1:8000/transfer" method="post">
<p>name:<input type="text" name="name"></p>
<p>money:<input type="text" name="money"></p>
<p>others:
<input type="text" >
<input type="hidden" name="others" value="mcc" style="display: none" >
</p>
<input type="submit">
</form>
</body>
</html>
django.middleware.csrf.CsrfViewMiddleware
中间件,他就是用来做校验的,他会每次都会在页面动态刷新生成一个value,然后每次用key值来比对这个value,通过了才会做下一步操作,不然的话就会直接拒绝这个请求,写法也很简单:
form表单写法
我们直接在form表单中写{% csrf token%},这个时候,django的中间件就会自动的帮我们去校验,他会每次都动态的生成一个验证码,只要页面刷新验证码就会不同,具体如下:
<!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>
<link rel="stylesheet" href="/static/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script src="/static/bootstrap-3.3.7-dist/js/bootstrap.min.js"></script>
</head>
<body>
<h1>正经网站</h1>
<form action="" method="post">
{% csrf_token %}
<p>name:<input type="text" name="name"></p>
<p>money:<input type="text" name="money"></p>
<p>others:<input type="text" name="others"></p>
<input type="submit">
</form>
</body>
</html>
这样的话,钓鱼网站怎么也访问不了
每次这个value都会刷新,这样的话钓鱼网站怎么也访问不了
Ajax校验
这个中间件这么神奇,但是我们中间也有一些函数不想校验,具体做法如下
from django.views.decorators.csrf import csrf_exempt,csrf_protect
FBV校验
单个视图函数取消校验:
@csrf_exempt #
def index(request):
pass
单个函数添加校验:
@csrf_protect
def login(request):
pass
CBV校验
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt,csrf_protect
单个类添加校验
三种方法都可以用,和正常的CBV装饰器方法一样
单个类取消校验
给CBV取消单个校验(csrf-exempt)
不能给单独的视图函数添加,只有dispatch可以,是因为给全局加的
直接给类加的,就要指定name=‘dispatch’
总结:其实都是给dispatch加,类就是一个路由,里面不可以单独写, --全部都要加到全局才可以
@method_decorator(csrf_exempt,name='dispatch') # 第一种
class Csrf_Token(View):
@method_decorator(csrf_exempt) # 第二种
def dispatch(self,request,*args,**kwargs):
res = super().dispatch(request,*args,**kwargs)
return res
@method_decorator(csrf_exempt) # 这里这么写不行!!!
def get(self,request):
pass
def post(self,request):
pass
Auth认证模块
Auth模块是Django自带的用户认证模块:
我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。
Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统–auth,它默认使用 auth_user 表来存储用户数据。
Auth模块常用的方法
from django.contrib import auth
authenticate()
提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。
如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。
authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
用法:
user = auth.authenticate(username='usernamer',password='password')
login(HttpRequest, user)
该函数接受一个HttpRequest对象,以及一个经过认证的User对象。
该函数实现一个用户登录的功能。它本质上会在后端为该用户生成相关session数据。
用法:
from django.contrib.auth import authenticate, login
def my_view(request):
username = request.POST['username']
password = request.POST['password']
#判断登录的用户是否存在,存在则返回对象,
user_obj = auth.authenticate(username=username, password=password)
if user_obj is not None:
#login(request, user)干了三件事,
#1生成密文session串存到数据库
#2将session键值对发送到浏览器端(sessioned,加密串),用于之后的登录判断,之后再访问有该串,则可以访问对应的资源
#3(就是视图函数中只要有request,就能访问request.user)就是request.user对象可以访问username,password等资源,以及request.user.is_authenticated,以及修改密码set_password("") request.user.save(),
auth.login(request, user)
# Redirect to a success page.
...
else:
# Return an 'invalid login' error message.
...
logout(request)
该函数接受一个HttpRequest对象,无返回值。
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
用法:
from django.contrib.auth import logout
def logout_view(request):
auth.logout(request)
# Redirect to a success page.
is_authenticated():
用来判断当前请求是否通过了认证。
def my_view(request):
if not request.user.is_authenticated():
return redirect(’%s?next=%s’ % (settings.LOGIN_URL, request.path))
login_requierd()
auth 给我们提供的一个装饰器工具,用来快捷的给某个视图添加登录校验。
用法
from django.contrib.auth.decorators import login_required
@login_required
def my_view(request):
...
若用户没有登录,则会跳转到django默认的 登录URL '/accounts/login/ ’ 并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
如果需要自定义登录的URL,则需要在settings.py文件中通过LOGIN_URL进行修改。
示例:
LOGIN_URL = '/login/' # 这里配置成你项目登录页面的路由
create_user()
auth 提供的一个创建新用户的方法
,需要提供必要参数(username、password)等。
用法:
from django.contrib.auth.models import User
user = User.objects.create_user(username='用户名',password='密码',email='邮箱',...)
create_superuser()
auth 提供的一个创建新的超级用户的方法,需要提供必要参数(username、password)等。
用法:
from django.contrib.auth.models import User
user = User.objects.create_superuser(username='用户名',password='密码',email='邮箱',...)
check_password(password)
auth 提供的一个检查密码是否正确的方法,需要提供当前请求用户的密码。
密码正确返回True,否则返回False。
用法:
ok = request.user.check_password('密码')
set_password(password)
auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。
注意:设置完一定要调用用户对象的save方法!!!
用法:
user.set_password(password='')
user.save()
User对象的属性
User对象属性:username, password
is_staff : 用户是否拥有网站的管理权限.
is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。
这内置的认证系统这么好用,但是auth_user表字段都是固定的那几个,我在项目中没法拿来直接使用啊!
比如,我想要加一个存储用户手机号的字段,怎么办?
聪明的你可能会想到新建另外一张表然后通过一对一和内置的auth_user表关联,这样虽然能满足要求但是有没有更好的实现方式呢?
答案是当然有了。
我们可以通过继承内置的 AbstractUser 类,来定义一个自己的Model类。
这样既能根据项目需求灵活的设计用户表,又能使用Django强大的认证系统了。
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
"""
用户信息表
"""
nid = models.AutoField(primary_key=True)
phone = models.CharField(max_length=11, null=True, unique=True)
def __str__(self):
return self.username
注:
按上面的方式扩展了内置的auth_user表之后,一定要在settings.py中告诉Django,我现在使用我新定义的UserInfo表来做用户认证。写法如下
# 引用Django自带的User表,继承使用时需要设置
AUTH_USER_MODEL = "app名.UserInfo"
一旦我们指定了新的认证系统所使用的表,原来的auth_user则不会再创建了。