Django-7
Django-7
Cookie
由来
由于http协议是无状态的,每次请求都是独立的,当用户访问浏览器网页时,并不会记录登录状态,所以,对服务器来说,你的每次请求都是全新的。
简介
那么,为了保存用户的登录状态以及信息,就有了cookie,cookie具体指的是一段小信息,他是服务器大宋出来存储在浏览器上的一组组键值对,下次访问服务器时浏览器会自动携带这些键值对,以便服务器提取有用的信息。
原理
由服务器产生内容,浏览器收到请求后保存在本地,当浏览器再次访问时,浏览器会自动带上cookie,这样服务器就能通过cookie的内容判断当前用户信息了。
查看cookie
F12打开浏览器开发者工具,在Aplication选项里的cookies能看到当前页面存储的cookies。
Django中操作cookie
获取cookie
request.COOKIES.get()
设置cookie
首先,你需要将页面返回的内容交给一个变量接收
obj = HttpResponse()
obj = render()
obj = redirect()
然后就可以通过这个对象来设置cookie,最后返回这个对象即可。
obj.set_cookie('k':'v'...)
删除cookie
同样需要用对象来操作。
obj.delete_cookie('key')
使用cookie实现用户登录认证装饰器功能
def login_auth(func):
@wraps(func)
def inner(request,*args,**kwargs):
if request.COOKIES.get('name'):
res = func(request,*args,**kwargs)
return res
else:
target_url = request.path_info
return redirect('/login/?next=%s'%target_url)
return inner
def login(request):
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'shabi' and password == '123':
old_path = request.GET.get('next')
if old_path:
obj = redirect(old_path)
else:
obj = redirect('/home/')
obj.set_cookie('name','shabi')
return obj
return render(request,'login.html')
@login_auth
def home(request):
'''模拟主页'''
return HttpResponse('我是主页,只有登录之后才能看!')
@login_auth
def logout(request):
'''注销'''
obj = redirect('/login/')
obj.delete_cookie('name')
return obj
session
由来
cookie虽然在一定程度上解决了保持状态的需求,但是由于cookie本身最大支持4096字节,以及cookie本身保存在客户端,可能被拦截或窃取,因此就需要有一种新东西,它能支持更多的字节,并且还能保存在服务器上,有较高的安全性,这就是session。
- session是保存在服务器上的键值对
- Django中,session默认的过期时间是14天
设置session
request.session['key'] = value
仅仅只会在内存中产生一个缓存,内部发生了三件事:
- Django内部自动生成了随机的字符串
- 在django_session表中存入数据
- session_key
- session_data
- date
- 将产生的随机字符串发送给浏览器,让浏览器保存到cookie中
获取session
request.session.get('key')
内部也发生了三件事:
- 浏览器发送cookie到Django后端之后,Django会自动获取到cookie值
- 拿着随机字符串去django_session表中比对,是否有对应的数据
- 如果比对上了,就将随机字符串所对应的数据取出并且赋值给request.session,如果没有校验成功,那么request.session取出来的就是空。
删除session
request.session.delete()
:只删除服务端的session
request.session.flush()
:浏览器和服务端的session会被全部清除
session设置超时时间
request.session.set_expiry(value)
value值:
- 如果value是个整数,session会在些秒数后失效。
- 如果value是个datatime或timedelta,session就会在这个时间后失效。
- 如果value是0,用户关闭浏览器session就会失效。
- 如果value是None,session会依赖全局session失效策略。
session版登录验证装饰器
from django import forms
class Myform(forms.Form):
username = forms.CharField(max_length=8,min_length=3)
password = forms.CharField(max_length=8,min_length=3)
def login(request):
form_obj = Myform()
if request.method == 'POST':
form_obj = Myform(request.POST)
if form_obj.is_valid():
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'nick' and password == '123':
old_path = request.GET.get('next')
request.session['user'] = username
if old_path:
obj = redirect(old_path)
else:
obj = HttpResponse('登录成功')
# obj.set_cookie('name','nick')
return obj
return render(request,'login.html',locals())
from functools import wraps
def login_auth(func):
@wraps(func)
def inner(request,*args,**kwargs):
# if request.COOKIES.get('name'):
if request.session.get('user'):
res = func(request,*args,**kwargs)
return res
else:
target_url = request.path_info
return redirect('/login/?next=%s'%target_url)
return inner
@login_auth
def home(request):
return HttpResponse('home')
@login_auth
def index(request):
return HttpResponse('index')
@login_auth
def logout(request):
request.session.delete()
return HttpResponse('注销成功!')
利用forms组件实现页面渲染以及数据校验
<form action="" method="post" novalidate>
{% csrf_token %}
{% for foo in form_obj %}
<p>{{ foo.label }}:{{ foo }}</p>
<span style="color: red">{{ foo.errors.0 }}</span>
{% endfor %}
<input type="submit">
</form>
注意:
- 在使用session之前,必须先执行数据库迁移命令生成django内置的django_session表。
- django_session表是针对浏览器的,不同浏览器来,生成的session都不一样。
Django中间件
什么是中间件
中间件是一个用来处理Django的请求和响应的框架级别的钩子。它是一个轻量,低级别的插件系统,用在于全局范围内概念Django的输入和输出。每个中间件组件都负责做一些特定的功能。
但是由于其影响的是全局,所以需要谨慎使用,使用不当会影响性能。
其实,中间件的实质就是在视图函数执行前后自动执行的一系列额外的操作。本质就是一个自定义类,类中定义了几个方法。
中间件的存放位置
打开Django项目的Settings.py文件,里面就存放着MIDDLEWARE配置,这就是中间件。它是以列表存储的几个字符串,这些字符串就是存放特定功能的中间件类的路径。我们也可以自定义中间件,只需将路径以字符串的形式在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中间件暴露给程序员五个可以自定义的方法,这五个方法都是特定条件下自动触发的。
自定义中间件以及方法介绍
新建一个文件夹,里面新建一个任意名称的py文件,里面写类,固定继承。
from django.utils.deprecation import MiddlewareMixin
class MyMiddle(MiddlewareMixin):
def process_request(self,request):
'''
请求来的时候,会自上而下依次经过每一个中间件里面的process_request。
一旦里面返回了一个HttpResponse对象,那么就不再往后执行了。
之后会执行同一级别的process_response。
'''
pass
def process_response(self,request,response):
'''
响应走的时候,会自下而上依次经过每个中间件里面的process_response。
此方法必须将response返回出去,因为这个response就是返回给用户的数据。
'''
pass
return response
def process_view(self,request,view_func,view_args,view_kwargs):
'''
路由匹配成功之后|执行视图函数之前触发
'''
pass
def process_exception(self,request,exception):
'''
当视图函数出现异常的时候自动触发
'''
pass
def process_template_response(self,request,response):
'''
当视图函数执行完毕之后,并且返回的对象中含有render方法的情况下才会触发。
'''
pass
return response
然后去配置文件中手动注册:'app01.mymiddleware.myaabb.MyMiddle1'
中间件的执行过程
有了中间件的Django请求流程图
跨站请求伪造(csrf)
钓鱼网站
伪造与真实的网站一模一样的web页面,比如,当你访问这个钓鱼网站中,页面会给你一个自定义个表单,比如转账,会给你暴露一个没有name属性的input框,取而代之的是提前写好的一个隐藏的带有name和value的input框,这样,你输入的数据就会被钓鱼网站非法篡改,并且会提交到真实的网站中。
解决策略
只要是用户想要提交post请求的页面,我在返回给用户响应的时候就提前设置好一个随机字符串,当用户提交post请求的时候,会自动先取查找是否有该随机字符串。
如果有,正常提交,没有的话,直接403拒绝访问(CsrfViewMiddleware)
CsrfViewMiddleware
当你提交post请求时,CsrfViewMiddleware中间件就会校验你的post请求有没有一个随机字符串,没有的话会直接给你forbidden。针对不同的情况,有不同的解决方法。
第一种情况:针对form表单
在form表单中添加csrf_token
<form action="" method="post">
{% csrf_token %}
{% for foo in form_obj %}
<p>{{ foo.label }}:{{ foo }}</p>
{% endfor %}
<input type="submit">
</form>
第一种情况:针对Ajax
方式一:
在html页面上先通过{% csrf_token %}获取到随机字符串,然后利用标签来查找。
data:{'username':'zzz','csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()}
方式二:
data:{'username':'jason','csrfmiddlewaretoken':'{{ csrf_token }}'}
这种方式只适合能提供csrf_token的python的Django后端,当前后端完全分离之后,前端也不支持Django的模板语法了,所以,这种方式在前后端分离之后不适用。
方式三:
通过Django官网的官方js文档来实现。
- 自定义static静态文件
- 在settings中配置静态文件
- 将js文件拷到static文件夹中
- 在html文件中引用js文件:
<script src="/static/setup.js"></script>
// 官方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);
}
}
});
局部使用csrfmiddleware中间件
from django.views.decorators.csrf import csrf_exempt, csrf_protect
两个装饰器:
- csrf_exempt:忽略。当csrf全局作用时,让csrf忽略此方法
- csrf_protect:保护。当没有cdrf作用时,让csrf保护此方法
针对FBV
直接在被装饰函数加装饰器即可
@csrf_exempt
def exem(request):
return HttpResponse('exempt')
@csrf_protect
def pro(request):
return HttpResponse('pro')
针对CBV
需要用到method_decorator装饰器
from django.views import View
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# 第一种
# @method_decorator(csrf_exempt,name='dispatch')
@method_decorator(csrf_protect,name='post')
class MyCsrf(View):
# 第二种
# @method_decorator(csrf_exempt)
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('hahaha')
@method_decorator(csrf_protect)
def post(self,request):
return HttpResponse('post')
"""
method_decorator()
指定了两个参数,第一个放装饰器名称,第二个放被装饰的函数名
如果放在类之外,必须指定name为类中的哪个方法。
如果直接放在函数中,可以不指定name参数。
"""