Django中间件,csrf校验和装饰器以及auth模块和部分功能
1).什么是中间件
官方的说法:中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量、低级别的插件系统,用于在全局范围内改变Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
说的直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作,它本质上就是一个自定义类,类中定义了几个方法,Django框架会在请求的特定的时间去执行这些方法。
我们一直都在使用中间件,只是没有注意到而已,打开Django项目的Settings.py文件,看到下图的MIDDLEWARE配置项。
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', ]
MIDDLEWARE配置项是一个列表(列表是有序的,记住这一点,后面你就知道为什么要强调有序二字),列表中是一个个字符串,这些字符串其实是一个个类,也就是一个个中间件。
因此:只要以后用django开发业务 设计到全局相关的功能 就考虑用中间件来完成,比如下面这些
-
全局用户身份校验
-
全局用户访问频率校验
-
用户访问黑名单
-
用户访问白名单
2)Django请求生命周期图
3).如何自定义中间键
自定义中间件我们需要去看一眼源码,找一下共同点
注意:找哪一个中间件的源码,我们需要将其导出来才能看到,比如SessionMiddleware
from django.contrib.sessions.middleware import SessionMiddleware
共同点:
class SessionMiddleware(MiddlewareMixin): def process_request(self, request): def process_response(self, request, response): class CsrfViewMiddleware(MiddlewareMixin): def process_request(self, request): def process_view(self, request, callback, callback_args, callback_kwargs): def process_response(self, request, response): class AuthenticationMiddleware(MiddlewareMixin): def process_request(self, request):
django运行用户自定义中间件并且暴露给用户五个可以自定义的方法
1.process_request和process_response(重点)
process_request(self, request):
请求过来的时候会按照配置文件中中间件从上到下的顺序依次执行每一个中间件中的process_request方法,如果没有直接跳到下一个
若在定义中间件的process_request直接返回一个HttpResponse之类的则直接跳过剩下的部分从当前中间件的porcess_response执行后直接返回
注意:Django是同级别直接返回,而flask是遇到这种情况会执行所有的process_response
process_response
响应走的时候会按照配置文件中注册的中间件从下往上的顺序依次执行每一个中间件里面的
process_response方法 该方法必须要有两个形参 并且需要将形参response返回
如果你内部自己返回了HttpResponse对象 会将返回给用户浏览器的内容替换成你自己的
示例:
-
新建一个自定义中间件文件夹,在这个文件夹下新建一个自定义中间件的py文件
from django.utils.deprecation import MiddlewareMixin class Mymid1(MiddlewareMixin): def process_request(self, request): print('我是第一个自定义中间件的process_request') 若有同级别的返回则直接跳过剩下的部分直接返回 # return HttpResponse('一个返回值') def process_response(self, request, response): print('我是第一个自定义中间件的process_response') return response class Mymid2(MiddlewareMixin): def process_request(self, request): print('我是第二个自定义中间件的process_request') def process_response(self, request, response): print('我是第二个自定义中间件的process_response') return response
-
然后在配置文件中的MIDDLEWARE中将自定义的中间件路径注册上
结果:
2)process_view,process_template_reponse,process_exception(了解)
# process_view方法 def process_view(self, request, view_func, view_args, view_kwargs): print('我是第一个中间件里面的process_view')
该方法有四个参数
request是HttpRequest对象。
view_func是Django即将使用的视图函数。 (它是实际的函数对象,而不是函数的名称作为字符串。)
view_args是将传递给视图的位置参数的列表.
view_kwargs是将传递给视图的关键字参数的字典。 view_args和view_kwargs都不包含第一个视图参数(request)。
它应该返回None或一个HttpResponse对象。 如果返回None,Django将继续处理这个请求,
执行任何其他中间件的process_view方法,然后在执行相应的视图。 如果它返回一个HttpResponse对象,
那么将不会执行Django的视图函数,而是直接在中间件中掉头,倒叙执行一个个process_response方法,最后返回给浏览器
总结:Django会在调用视图函数之前调用process_view方法。
def process_exception(self,request,exception): print('exception:',exception) print('我是第一个中间件里面的process_exception') 该方法两个参数: 一个HttpRequest对象 一个exception是视图函数异常产生的Exception对象。 这个方法只有在视图函数中出现异常了才执行,
它返回的值可以是一个None也可以是一个HttpResponse对象。
如果是HttpResponse对象,Django将调用模板和中间件中的process_response方法,
并返回给浏览器,否则将默认处理异常。如果返回一个None,则交给下一个中间件的process_exception方法来
总结:这个方法只有在视图函数中出现异常了才执行
# 自定义中间件方法 def process_template_response(self,request,response): print('我是第一个中间件里面的process_template_reponse方法') return response
# views def index(request): print('我是视图函数index') def render(): return HttpResponse("你好啊 我是index里面的render函数") obj = HttpResponse("index") obj.render = render return obj
总结:process_template_response(self, request, response)
它的参数,一个HttpRequest对象,response是TemplateResponse对象(由视图函数或者中间件产生)。
process_template_response是在视图函数执行完成后立即执行,但是它有一个前提条件,那就是视图函数返回的对象有一个render属性对应的render方法(或者表明该对象是一个TemplateResponse对象或等价方法)。
二.csrf跨站请求伪造
了解一下基本的钓鱼网站
钓鱼网站
本质搭建一个跟正常网站一模一样的页面
用户在该页面上完成转账功能
转账的请求确实是朝着正常网站的服务端提交
唯一不同的在于收款账户人不同
给用户书写form表单 对方账户的input没有name属性
你自己悄悄提前写好了一个具有默认的并且是隐藏的具有name属性的input
模拟真实网站和钓鱼网站
先将django配置文件里面的'django.middleware.csrf.CsrfViewMiddleware'
注释了
# 真实网站 # views.py def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get('money') print('%s 给 %s 转了 %s元' %(username, target_user, money)) return render(request, 'transfer.html') # transfer.html <h2>我是真实的网站</h2> <form action="" method="post"> <p>username:<input type="text" name="username"></p> <p>target_user:<input type="text" name="target_user"></p> <p>money<input type="text" name="money"></p> <input type="submit"> </form> # 钓鱼网站 # views.py def transfer(request): if request.method == 'POST': username = request.POST.get('username') target_user = request.POST.get('target_user') money = request.POST.get("money") print('%s 给 %s 转了 %s元'%(username,target_user,money)) return render(request,'transfer.html') # transfer.html <h2>我是钓鱼网站</h2> # 将传输数据的网站改为真实网站网址 <form action="http://127.0.0.1:8000/transfer/" method="post"> <p>username:<input type="text" name="username"></p> <p>target_user:<input type="text"></p> # 在转账人下面新建一个隐藏的input框,默认值为jason,这样上面这个框就失效了 <input type="text" name="target_user" style="display: none" value="jason"> <p>money:<input type="text" name="money"></p> <input type="submit"> </form>
这样以上述代码就能实现他人用钓鱼网站来非法获取你的转账信息并加以修改,那么如何改变这种情况
# 只要在上面的form表单中加上下面这个{% csrf_token %},就可以启动django配置中的django.middleware.csrf.CsrfViewMiddleware中间件,这样每次访问服务端都会随机产生一个字符串,用于识别身份(注意是没有绝对安全的web的) {% csrf_token %}
-
form表单通过csrf校验
{% csrf_token %}
-
ajax通过csrf校验
# 写一个ajax请求 <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', data:{'username':'jason'}, success:function (data) { alert(data) } }) }) </script>
第一种方法:手动获取
<script> $('#d1').click(function () { $.ajax({ url:'', type:'post', data:{'username':'jason','csrfmiddlewaretoken':$('input[name="csrfmiddlewaretoken"]').val()}, success:function (data) { alert(data) } }) }) </script>
第二种方法:利用模板语法(本地写小项目推荐)
<script> $('d1').click(fnction(){ $.ajax({ url:'', type:'post', data:{'username':'jason','csrfmiddlewaretoken':''{{csrf_token}}''}, success:function(data){ alert(data) } }) }) </script>
第三种方法:写一个静态配置js文件引用过来(官网提供的,推荐使用,重点是知道复制第三步里面的js文件内容)
#第一步,配置静态文件 设置一个static文件夹 #第二步,在settings文件中配置静态文件路径 STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'satic') ] # 第三步,在static文件夹下新建一个配置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); } } }); #第四步,在ajax请求中引入这个js文件 {% load static %} <script src="{% static 'myset.js' %}"></script> <script> $('#d1').click(function () { $.ajax({ url:'', type:'post', data:{'username':'jason'}, success:function (data) { alert(data) } }) }) </script>
三.csrf装饰器扩展
-
FBV设置csrf装饰器
导入模块
from django.views.decorators.csrf import csrf_exempt, csrf_protect
当我们网站整体都校验csrf的时候 我想让某几个视图函数不校验
# 在指定视图函数上面添加下面这个装饰器 @csrf_exempt
当我们网站整体都不校验csrf的时候 我想让某几个视图函数校验
# 可以将中间件 'django.middleware.csrf.CsrfViewMiddleware',注释掉 # 然后在指定视图函数上面添加下面这个装饰器 @csrf_protect
-
如何将CBV设置csrf装饰器
导入模块
from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.utils.decorators import method_decorator # 不论是自己写的装饰器还是别人写好的装饰器都要记得用这个method_decorator方法
回忆一下CBV如何写的
from django.views import View class MyHome(View): def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post') 注意在urls.py文件中的写法 url(r'^home/',views.MyHome.as_view()),
如何让某个方法添加上csrf功能
@method_decorator(csrf_protect, name=post) # 第一种方法:指定某个方法名添加csrf class MyHome(View): def get(self,request): return HttpResponse('get') @method_decorator(csrf_protect) #第二种方法:直接在方法名上添加装饰器 def post(self,request): return HttpResponse('post')
from django.views import View from django.utils.decorators import method_decorator @method_decorator(csrf_protect,name='dispatch') # 第三种方法这样写也可以 class MyHome(View): @method_decorator(csrf_protect) # 第三种方法 类中所有的方法都装 def dispatch(self, request, *args, **kwargs): return super().dispatch(request,*args,**kwargs) def get(self,request): return HttpResponse('get') def post(self,request): return HttpResponse('post')
补充注意:上面的方法都是添加csrf校验,对于如何指定某个CBV中方法不校验,上面的一二两种方法都不行,只能通过第三种方法给dispatch装,将里面的csrf_protect改为csrf_exempt即可
四.auth模块相关功能
-
直接进行数据库迁移命令我们可以看到数据库中自行创建的一些表格
-
创建超级用户
第一步:点击快捷方式
-
在控制台输入命令
createsuperuser
创建成功的样子
django后台管理界面
-
auth模块注册功能
# 第一步导入auth模块和auth模块中的User表 from django.contrib import auth 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.objects.create(username=username,password=password) # 不能使用 密码变成明文的了 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模块其他功能
# 登陆认证 def login(request): if request.method == 'POST': username = request.POST.get('username') password = request.POST.get('password') # 明文 # 数据库校验用户名和密码是否正确,这样是用明文校验是不对的 # User.objects.filter(username=username,password=password) # 通过auth的内置方法来校验 user_obj = auth.authenticate(request,username=username,password=password) print(user_obj) """ 用户名密码正确返回的是用户对象 错误返回None """ print(res.username) print(res.password) if user_obj: # 保存用户登录状态 # request.session auth.login(request,user_obj) """ 只要执行了这一句话 之后在任意可以获取到request对象的地方 都可以通过request.user获取到当前登录的用户对象 """ return HttpResponse("登录成功") return render(request,'login.html') def get_user(request): print(request.user) """ 用户登录成功之后 request.user拿到的就是用户对象 没有登录 获取到的匿名用户 """ print(request.user.is_authenticated()) # 返回的是布尔值 return HttpResponse("get_user") # 局部配置校验用户是否登录装饰器 # 先导入模块 from django.contrib.auth.decorators import login_required # 若里面不传指定login_url参数就会导入到django内置的的登陆界面 #@login_required @login_required(login_url='/login/') # 这样可以跳转到自定义的登陆界面 def xxx(request): return HttpResponse('xxx页面') # @login_required @login_required(login_url='/login/') def yyy(request): return HttpResponse('yyy页面') #@login_required @login_required(login_url='/login/') def zzz(request): return HttpResponse('zzz页面') # 如何设置全局配置校验用户是否登录装饰器 # 在settings.py文件中写入下面这个代码 LOGIN_URL = '/login/' # 然后在指定视图函数上只要写下面这个就可以看,不需要在指定url了 @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') # 1 先校验旧密码是否正确 is_right = request.user.check_password(old_password) # print(is_right) 是布尔值 # 2 再去修改新密码 if is_right: request.user.set_password(new_password) request.user.save() # 一定要save一下 否则数据库无影响 return render(request,'set_password.html') # 如何注销登陆状态 @login_required def logout(request): auth.logout(request) return HttpResponse('注销成功')
-
如何将默认设置的user表进行扩展
# 第一种方法可以利用一对一表关系,就是新建一张表,与其是一对一关系,则两张表就是一张表,实现了扩展字段功能 #第二种,类的继承 # 在models.py文件中导入下面模块(实际上原先的django默认设置的表都是来源于AbstracterUser,因此我们也可以继承这个类来创建自己的表) from django.contrib.auth.models import User,AbstractUser # Create your models here. class Userinfo(AbstractUser): phone = models.BigIntegerField() avatar = models.FileField() # 扩展的字段 尽量不要与原先表中的字段冲突 # 然后在配置文件写上下面这个代码 AUTH_USER_MODEL = 'app01.Userinfo' # AUTH_USER_MODEL = '应用名.表名' """ django就会将userinfo表来替换auth_user表 并且之前auth模块所有的功能不变 参照的也是userinfo表 """