1 django中间件
1.1 中间件介绍
# django中间件是django的门户
1.请求来的时候需要先经过中间件才能到达真正的django后端
2.响应走的时候最后也需要经过中间件才能发送出去
# 什么时候使用django中间件
当我们需要给web后端添加一些全局相关的功能时可以使用中间件
# eg:
1.全局用户身份校验 每个用户的登录状态
2.全局用户权限校验
3.全局访问频率校验 每个用户的访问频率
4.用户黑名单、白名单
# 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中间件代码规律:
每个中间件基本都有process_request和process_response方法
class SessionMiddleware(MiddlewareMixin):
def process_request(self, request):
session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
return response
class CsrfViewMiddleware(MiddlewareMixin):
def process_request(self, request):
csrf_token = self._get_token(request)
if csrf_token is not None:
# Use same token next time.
request.META['CSRF_COOKIE'] = csrf_token
def process_view(self, request, callback, callback_args, callback_kwargs):
return self._accept(request)
def process_response(self, request, response):
return response
class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
request.user = SimpleLazyObject(lambda: get_user(request))
# django支持自定义中间件,并且暴露五个可以自定义的方法
1.必须掌握
process_request
process_response
2.了解即可
process_view
process_template_response
process_exception
1.2 自定义中间件
# 步骤:
1.在项目名或者应用名下创建一个任意名称的文件夹
2.在该文件夹内创建一个任意名称的py文件
3.在该py文件内需要定义类 (这个类必须继承MiddlewareMixin)
from django.utils.deprecation import MiddlewareMixin
然后在这个类里,就可以自定义五个方法了
(这五个方法并不是全部都需要书写,用几个写几个)
4.需要将类的路径以字符串的形式,注册到配置文件中才能生效
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',
'你自己写的中间件的路径1',
'app01.mymiddleware.MyMdd1',
'你自己写的中间件的路径3',
]
# 中间件类的五个方法
# 1.必须掌握
# process_request
1.请求来的时候需要经过每一个中间件里面的process_request方法
该方法有1个参数request
执行顺序是按照配置文件中注册的中间件从上往下依次执行
2.如果中间件里面没有定义该方法,那么直接跳过执行下一个中间件
3.如果该方法返回了HttpResponse对象,那么请求将不再继续往后执行
而是直接原路返回 (eg: 校验失败不允许访问...)
# process_request方法就是用来做全局相关的所有限制功能
# process_response
1.响应走的时候需要经过每一个中间件里面的process_response方法
该方法有两个参数request,response
2.该方法必须返回一个HttpResponse对象
1.默认返回的就是形参response
2.也可以返回自己的
3.执行顺序是按照配置文件中注册了的中间件从下往上依次经过
如果你没有定义的话 直接跳过执行下一个
# 研究如果在第一个process_request方法就已经返回了HttpResponse对象,
那么响应走的时候是经过所有的中间件里面的process_response还是有其他情况 ?
# 答:会直接走同级别的process_reponse返回
# flask框架也有一个中间件但是它的规律
只要返回数据了就必须经过所有中间件里面的类似于process_reponse方法
# 2.了解即可
# process_view
路由匹配成功之后 执行视图函数之前,会自动执行中间件里面的该方法
执行顺序是按照配置文件中注册的中间件从上往下
# process_template_response
当视图函数中返回的HttpResponse对象 有render属性的时候 才会触发
执行顺序是按照配置文件中注册了的中间件从下往上
# process_exception
当视图函数中出现异常的情况下触发
执行顺序是按照配置文件中注册了的中间件从下往上
2 csrf跨站请求伪造
2.1 csrf介绍
# csrf:Cross-site request forgery
跨站请求伪造校验
网站在给用户返回一个具有提交数据功能页面的时候
会给这个页面加一个唯一标识
当这个页面朝后端发送post请求的时候
后端会先校验唯一标识,如果唯一标识不对直接拒绝 (403 forbbiden)
如果成功则正常执行
# csrf由来:
钓鱼网站
我搭建一个跟正规网站一模一样的界面 (eg:中国银行)
用户不小心进入到了我们的网站,用户给某个人打钱
打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
但是唯一不同的时候打钱的账户不是用户想要打的账户,而变成了一个莫名其妙的账户
内部本质
我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
然后我们在内部隐藏一个已经写好name和value的input框
如何规避上述问题
csrf跨站请求伪造校验
# Django 会在post请求时,进行csrf跨站请求伪造校验
默认通过中间件 django.middleware.csrf.CsrfViewMiddleware
# CsrfViewMiddleware中间件的校验原理:
1.Django后端响应页面时,会给前端响应 带有 名为"csrftoken"的cookie
2.前端发送数据时,后端会从请求的两个地方获取 两者都 没有或校验错误,才会报crsf校验错误
2.1 从请求的数据中,判断是否含有 名为'csrfmiddlewaretoken'的值
2.2 或从请求的请求头中,判断是否含有 名为 "X-CSRFToken"的请求头
3.后端会分别对 该两个随机字符串(加密方式相似但不同),通过salt进行解密出唯一的secret
3.1 cookie中的"csrftoken" 64位随机字符串 (32位salt+32位密文)
3.2 提交的'csrfmiddlewaretoken' token 64位随机字符串 (32位salt+32位密文)
是否能解出同样的secret,若secret一样则本次请求合法。
2.2 如何符合校验
# form表单如何符合校验
模板语法:{% csrf_token %}
# eg:
<form action="" method="post">
{% csrf_token %}
<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>
2.2.2 ajax符合校验
前端 获取 csrf_token值
### 1.若是前后端混合项目,csrfmiddlewaretoken值 由Django生成,可采用 第一、二种方式
# 第一种: 利用jQuery的标签查找 获取页面上的随机字符串,并放进post的data数据里
data: {
"username":'jason',
'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()
}
# 第二种: 利用模版语法提供的快捷书写
data:{
"username":'jason',
'csrfmiddlewaretoken':'{{ csrf_token }}'
}
### 2.若是前后端分类项目,前端通过请求,获取Django生成的csrfmiddlewaretoken值
# 第三种: 通过前端Ajax请求,获取django生成的 csrfmiddlewaretoken值
$.ajax({
url: "/get_csrf_token/",
success:function (res) {
console.log('csrfmiddlewaretoken':res)
// 存储到本地,方便Ajax发生真正请求时,构造csrf_token的键值对
localStorage.setItem("csrf_token", res)
}
})
from django.middleware.csrf import get_token
from django.http import JsonResponse
def get_csrf_token(request):
csrf_token = get_token(request)
return HttpResponse(csrf_token)
方式1:csrf_token值,放在前端 Ajax请求的数据中
# 针对不同数据类型,Ajax的csrf校验可使用方法
# 1.Ajax发普通键值对, contentType:'urlencoded'时:直接使用
# 2.Ajax发文件时,注意: 数据是需要添加到 formdata对象中
formDataobj.append('csrfmiddlewaretoken', '{{ csrf_token }}')
# 3.Ajax发json时,只能采用方式2--放请求头中(可自己写,也可直接用方式2的js脚本)
因为Ajax发json格式,需要前端将数据进行json序列化 JSON.stringify(data),
若csrf校验数据放在data里面,Django后端无法识别了
(数据获取由 request.POST 变成了 request.body)
# 先请求获取csrf_token值,再放进请求头中发生真正的数据请求
$.ajax({
url: "/login/",
method: "post",
headers:{"X-CSRFToken":localStorage.getItem("csrf_token")},
data: {
user:"admin",
pwd:"123"
},
success:function (res) {
console.log(res)
}
})
方式2:csrf_token值,放在前端 Ajax请求的请求头中
// 请求的数据,就不需要包含 "csrfmiddlewaretoken"的键值对,放原本的数据即可
data:{
"username":'jason',
}
// csrf.js: 通用方式 拷贝下面js代码在script中
// 1.从浏览器中的cookie中,获取名为'csrftoken'的cookie值
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');
// 2.jquery的ajax请求中封装了一个方法:ajaxSetup,可以为所有的ajax请求做一个集体配置
// 为所有ajax请求,都添加了"X-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);
}
}
});
2.3 csrf相关装饰器
from django.views.decorators.csrf import csrf_protect,csrf_exempt
# csrf_protect 需要校验
针对csrf_protect,CBV符合装饰器的三种玩法
# csrf_exempt 忽视校验
针对csrf_exempt,CBV只能给dispatch方法加才有效
# FBV: 函数正常加装饰器的形式
# @csrf_exempt
# @csrf_protect
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')
# CBV:
csrf_protect: 三种都可以
csrf_exempt : 只能加在dispatch方法上
"""
1.网站整体都不校验csrf,就单单几个视图函数需要校验
2.网站整体都校验csrf,就单单几个视图函数不校验
"""
from django.views.decorators.csrf import csrf_protect,csrf_exempt
from django.utils.decorators import method_decorator
from django.views import View
# @method_decorator(csrf_protect,name='post') # 针对csrf_protect 第二种方式可以
# @method_decorator(csrf_exempt,name='post') # 针对csrf_exempt 第二种方式不可以
@method_decorator(csrf_exempt,name='dispatch')
class MyCsrfToken(View):
# @method_decorator(csrf_protect) # 针对csrf_protect 第三种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第三种方式可以
def dispatch(self, request, *args, **kwargs):
return super(MyCsrfToken, self).dispatch(request,*args,**kwargs)
def get(self,request):
return HttpResponse('get')
# @method_decorator(csrf_protect) # 针对csrf_protect 第一种方式可以
# @method_decorator(csrf_exempt) # 针对csrf_exempt 第一种方式不可以
def post(self,request):
return HttpResponse('post')
3 基于中间件的'插拔式'编程思想
# 中间件:添加字符串 进行功能注册,注释即功能关闭
'包.模块.类'
利用 字符串导入模块 + 反射机制
import settings
import importlib
def send_all(content):
for path_str in settings.NOTIFY_LIST: # 'notify.email.Email'
module_path,class_name = path_str.rsplit('.',maxsplit=1)
# module_path = 'notify.email' class_name = 'Email'
# 1 利用字符串导入模块
module = importlib.import_module(module_path) # from notify import email
# 2 利用反射获取类名
cls = getattr(module,class_name) # Email、QQ、Wechat
# 3 生成类的对象
obj = cls()
# 4 利用鸭子类型直接调用send方法
obj.send(content)
补充:字符串导入模块
# 模块:importlib
import importlib
res = 'myfile.b'
# 可以将字符串,按照模块来导入 但注意:该方法最小只能到py文件名 (即模块)
ret = importlib.import_module(res) # from myfile import b
print(ret)