Django之中间件及csrf跨站请求伪造、auth模块
Django中间件
一、什么是中间件?
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
django中间件是类似于是django的保安
请求的时候需要先经过中间件才能到达django后端(urls,views,templates,models)
响应走的时候也需要经过中间件才能到达web服务网关接口
作用:
说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
django默认中间件
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', ]
django请求生命周期
PS:
(1)上述当客户端的请求到达中间件的时候 其首先会查找缓存数据库
(2)如果缓存数据库查找不到 最终到达数据库查找
(3)当数据库将查找的数据返回 经过中间件的时候 其会将数据分成两份
(4)一份存入缓存数据库中 一份传输给客户端
(5)下次客户端再次获取相同的数据的时候 其不会在进行数据库查询 而是直接去缓存数据库查询
二、自定义中间件
django中间件中有五个用户可以自定义的方法
django中间件可以用来做什么(******)
1.网站全局的身份校验,访问频率限制,权限校验...只要是涉及到全局的校验你都可以在中间件中完成
2.django的中间件是所有web框架中做的最好的
常见自定义方法
(1)process_request
(2)process_view(self, request, view_func, view_args, view_kwargs)
(3)process_template_response(self,request,response)
(4)process_exception(self, request, exception)
(5)process_response(self, request, response)
需要我们掌握的方法有
1.process_request()方法
规律:
1.请求来的时候,会经过每个中间件里面的process_request方法(从上往下)
2.如果方法里面直接返回了HttpResponse对象,那么会直接返回,不再往下执行
基于该特点就可以做访问频率限制,身份校验,权限校验
2.process_response()方法
规律:
1.必须将response形参返回,因为这个形参指代的就是要返回给前端的数据
2.响应走的时候,会依次经过每一个中间件里面的process_response方法(从下往上)
需要了解的方法有
3.process_view()
在路由匹配成功执行视图函数之前触发
4.process_exception()
当你的视图函数报错时,就会自动执行
5.process_template_response()
当你返回的HttpResponse对象中必须包含render属性才会触发
def index(request): print('我是index视图函数') def render(): return HttpResponse('什么鬼玩意') obj = HttpResponse('index') obj.render = render return obj
总结:你在书写中间件的时候,只要形参中有repsonse,你就顺手将其返回,这个reponse就是要给前端的消息
如何自定义我们自己的中间件
研究这上面五个方法都有哪些特点
1.如果你想让你写的中间件生效,就必须要先继承MiddlewareMixin
2.在注册自定义中间件的时候,一定要确保路径不要写错
process_request
process_request有一个参数,就是request,这个request和视图函数中的request是一样的(在交给Django后面的路由之前,对这个request对象可以进行一系列的操作)。
由于request对象是一样的,所以我们可以对request对象进行一系列的操作,包括request.变量名=变量值,这样的操作,我们可以在后续的视图函数中通过相同的方式即可获取到我们在中间件中设置的值。
它的返回值可以是None也可以是HttpResponse对象。返回值是None的话,按正常流程继续走,交给下一个中间件处理,如果是HttpResponse对象,Django将不执行视图函数,而将相应对象返回给浏览器。
-
中间件的process_request方法是在执行视图函数之前执行的。
- 当配置多个中间件时,会按照MIDDLEWARE中的注册顺序,也就是列表的索引值,从前到后依次执行的。
- 不同中间件之间传递的request都是同一个对象
process_response
多个中间件中的process_response方法是按照MIDDLEWARE中的注册顺序倒序执行的,也就是说第一个中间件的process_request方法首先执行,而它的process_response方法最后执行,最后一个中间件的process_request方法最后一个执行,它的process_response方法是最先执行。
定义process_response方法时,必须给方法传入两个形参,request和response。request就是上述例子中一样的对象,response是视图函数返回的HttpResponse对象(也就是说这是Django后台处理完之后给出一个的一个具体的视图)。该方法的返回值(必须要有返回值)也必须是HttpResponse对象。如果不返回response而返回其他对象,则浏览器不会拿到Django后台给他的视图,而是我的中间件中返回的对象
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方法,最后返回给浏览器
process_view方法是在Django路由系统之后,视图系统之前执行的,执行顺序按照MIDDLEWARE中的注册顺序从前到后顺序执行的
process_exception
process_exception(self, request, exception)
该方法两个参数:
一个HttpRequest对象
一个exception是视图函数异常产生的Exception对象。
这个方法只有在视图函数中出现异常了才执行,它返回的值可以是一个None也可以是一个HttpResponse对象。如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来处理异常。它的执行顺序也是按照中间件注册顺序的倒序执行。
process_template_response(用的比较少)
process_template_response(self, request, response)
它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render()方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
三、中间件的执行流程图示
上一部分,我们了解了中间件中的5个方法,它们的参数、返回值以及什么时候执行,现在总结一下中间件的执行流程。
请求到达中间件之后,先按照正序执行每个注册中间件的process_request方法,process_request方法返回的值是None,就依次执行,如果返回的值是HttpResponse对象,不再执行后面的process_request方法,而是执行当前对应中间件的process_response方法(注意不是掉头执行所有的process_response方法),将HttpResponse对象返回给浏览器。也就是说:如果MIDDLEWARE中注册了6个中间件,执行过程中,第3个中间件返回了一个HttpResponse对象,那么第4,5,6中间件的process_request和process_response方法都不执行,顺序执行3,2,1中间件的process_response方法。
process_request方法都执行完后,匹配路由,找到要执行的视图函数,先不执行视图函数,先执行中间件中的process_view方法,process_view方法返回None,继续按顺序执行,所有process_view方法执行完后执行视图函数。假如中间件3 的process_view方法返回了HttpResponse对象,则4,5,6的process_view以及视图函数都不执行,直接从最后一个中间件,也就是中间件6的process_response方法开始倒序执行。
process_template_response和process_exception两个方法的触发是有条件的,执行顺序也是倒序。总结所有的执行流程如下:
csrf跨站请求伪造
钓鱼网站
通过制作一个跟正儿八经的网站一模一样的页面,骗取用户输入信息,转账交易
从而做手脚
转账交易的请求确确实实是发给了中国银行,账户的钱也是确确实实少了,唯一不一样的地方在于收款人账户不对
内部原理
在让用户输入对方账户的那个input上面做手脚
给这个input不设置name属性,在内部隐藏一个实现写好的name和value属性的input框
这个value的值,就是钓鱼网站受益人账号
防止钓鱼网站的思路
网站会给返回给用户的form表单页面,偷偷塞一个随机字符串
请求到来的时候,会先比对随机字符串是否一致,如果不一致,直接拒绝(错误403)
该随机字符串有以下特点
1.同一个浏览器每一次访问都不一样
2.不同浏览器绝对不会重复
一、form表单发送post请求的时候,需要你做得仅仅书写一句话
{% csrf_token %}
二、ajax发送post请求,如何避免csrf校验
三种方式:
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); } } });
三、跨站请求伪造相关装饰器
1.当你网站全局都需要校验csrf的时候,有几个不需要校验该如何处理
2.当你网站全局不校验csrf的时候,有几个需要校验又该如何处理

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')
总结:装饰器中只有csrf_exempt是特例,其他的装饰器在给CBV装饰的时候,都可以有三种方式
auth认证模块
一、Auth模块是什么
Auth模块是Django自带的用户认证模块:
我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这还真是个麻烦的事情呢。
Django作为一个完美主义者的终极框架,当然也会想到用户的这些痛点。它内置了强大的用户认证系统--auth,它默认使用 auth_user 表来存储用户数据。
二、auth模块常用方法
from django.contrib import auth
authenticate()
提供了用户认证功能,即验证用户名以及密码是否正确,一般需要username 、password两个关键字参数。
如果认证成功(用户名和密码正确有效),便会返回一个 User 对象。
authenticate()会在该 User 对象上设置一个属性来标识后端已经认证了该用户,且该信息在后续的登录过程中是需要的。
用法:
user = 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 = authenticate(username=username, password=password) if user is not None: 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): 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 = user.check_password('密码')
set_password(password)
auth 提供的一个修改密码的方法,接收 要设置的新密码 作为参数。
注意:设置完一定要调用用户对象的save方法!!!
用法:
user.set_password(password='') user.save()

@login_required def set_password(request): user = request.user err_msg = '' if request.method == 'POST': old_password = request.POST.get('old_password', '') new_password = request.POST.get('new_password', '') repeat_password = request.POST.get('repeat_password', '') # 检查旧密码是否正确 if user.check_password(old_password): if not new_password: err_msg = '新密码不能为空' elif new_password != repeat_password: err_msg = '两次密码不一致' else: user.set_password(new_password) user.save() return redirect("/login/") else: err_msg = '原密码输入错误' content = { 'err_msg': err_msg, } return render(request, 'set_password.html', content)
User对象的属性
User对象属性:username, password
is_staff : 用户是否拥有网站的管理权限.
is_active : 是否允许用户登录, 设置为 False,可以在不删除用户的前提下禁止用户登录。
三、扩展默认的auth_user表
这内置的认证系统这么好用,但是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表了。