csrf跨站请求伪造、csrf相关装饰器、auth认证模块、auth_user表切换以及基于django中间件设计项目功能
csrf跨站请求伪造
什么是跨站请求伪造
假的网站模拟真网站页面通过提交请求路径到真网站服务器,给服务器虚假信息
模拟钓鱼网站
假设是一个跟银行一模一样的网址页面,用户在该页面上转账,账户的钱会减少,但是受益人却不是自己想要转账的那个人。
可以用两个服务器端口分别充当正规网站服务器以及假网站服务器,并启动,然后将钓鱼网站的提交地址改成正规网站的地址。
假网站页面
<form action="/normal/" method="post"> # action后面的路径为提交请求的路径
<p>转账人<input type="text" name="transfer"></p>
<p>收款人 <input type="text"> # 无论填写什么读不回被接收到
<input type="text" name="collection" value="jason" style="display: none"> # 后端真正拿到数据的隐藏标签,但是浏览器上看不到(用检查可以看到)
</p>
<p>金额 <input type="text" name="money"></p>
<p><input type="submit"></p>
</form>
预防csrf跨站请求伪造
csrf策略
即通过在返回的页面上添加独一无二的表示信息(csrf的随机字符串)从而区分正规网站和钓鱼网站的请求,就可以阻止跨站请求伪造。
操作流程
- django自带csrf的中间件,需要确保不被注释掉
MIDDLEWARE = [
'django.middleware.csrf.CsrfViewMiddleware',
]
- 需要在form表单下写csrf的模版语法
{% csrf_token %}
<form action="" method="post">
{% csrf_token %} #
<p>转账人<input type="text" name="transfer"></p>
<p>收款人<input type="text" name="collection"></p>
<p>金额 <input type="text" name="money"></p>
<p><input type="submit"></p>
</form>
- 查看该form表单的前端页面,看
{% csrf_token %}
模版语法的前端具体展示
我们由图可以看出,{% csrf_token %}
其实就是一个input框,并且这个input框的value每当页面刷新一次都会更新,数据提交后,后端会收到带有csrf随机字符串的值.
- 后端获取csrf的input框value的两种方式
- form表单
<form action="" method="post">
{% csrf_token %}
</form>
- ajax
方式1:
{% csrf_token %}
<button id="d1">按钮</button>
<script>
$('#d1').click(function () {
$.ajax({
url:'',
type:'post',
data:{'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},
success:function () {
}
})
})
</script>
方式2:
# 这时候就不需要写:{% csrf_token%},因为下面的模版语法可以获取到随机的csrf认证的字符串
data:{'csrfmiddlewaretoken': {{ csrf_token }}},
方式3 :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);
}
}
});
导入这个js文件,然后就不需要在页面上写csrf相关模版语法,该js文件会自动处理
# 导入js文件
{% load static %}
<script src="{% static '/csrf.js' %}"></script>
csrf相关装饰器
- csrf_protect:校验csrf
- csrf_exempt:不校验csrf
FBV相关csrf装饰器
当整个网站默认都不校验csrf,但是局部视图函数需要校验,如何处理
settings的csrf中间件需要注释掉,然后需要导入以下模块
from django.views.decorators.csrf import csrf_protect
然后装饰在需要校验的视图函数上
# 视图函数
@csrf_protect
def home(request):
return render(request,'h3.html')
#模版层
<form action="" method="post">
<p>username: <input type="text" name="username"></p>
<input type="submit" value="确定">
</form>
这样当浏览器页面提交请求的时候就会需要在页面用csrf模版语法产生随机字符串让csrf中间件去认证
当整个网站默认都校验csrf,但是局部视图函数不需要校验,如何处理
settings的csrf需要注册(不被注释),需要导入以下模块
from django.views.decorators.csrf import csrf_exempt
然后装饰在不需要校验的视图函数上
@csrf_exempt
def home(request):
return render(request,'h3.html')
这时候,home这个视图函数就不需要从前端发出一个随机字符串来认证csrf了。
CBV相关csrf装饰器
针对CBV不能再方法上添加装饰器,需要借助专门添加装饰器的方法:method_decorator
未装饰装饰器代码:
# 路由层
urlpatterns = [path('index/',views.Index.as_view())]
# 视图类
from django.utils.decorators import method_decorator
from django import views
class Index(views.View):
def get(self,request):
return HttpResponse('get请求')
def post(self,request):
return HttpResponse('post请求')
视图类装饰csrf_protect
csrf中间件没注册,需要在类中装饰csrf_protect
装饰器,有如下三种方法
- 指名道姓的添加,在请求方法上,影响的是当前添加的方法
class Index(views.View):
def get(self,request):
return HttpResponse('get请求')
@method_decorator(csrf_protect) ---->
def post(self,request):
return HttpResponse('post请求')
- 在类上添加,指名道姓的添加,用
name='xxx'
指定,影响的是name指定的方法
@method_decorator(csrf_protect,name='post')
class Index(views.View):
- 重写父类中dispatch方法,影响类中所有的方法
dispatch方法会得到类型所有的请求方法,并执行对应请求方法的函数体,所以它的影响是当前类中方法的全部
class Index(views.View):
@method_decorator(csrf_protect)
def dispatch(self, request, *args, **kwargs):
return super(Index, self).dispatch(request, *args, **kwargs)
def get(self,request):
return HttpResponse('get请求')
# @method_decorator(csrf_protect)
def post(self,request):
return HttpResponse('post请求')
视图类装饰csrf_exempt
类中装饰csrf_exempt就比较特殊,就没有csrf_protect那么多方法,它只能加在类中重写的dispatch
方法上
class Index(views.View):
@method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(Index, self).dispatch(request, *args, **kwargs)
def get(self,request):
return HttpResponse('get请求')
def post(self,request):
return HttpResponse('post请求')
针对csrf_exempt只有dispatch方法上加有效,针对其他装饰器上述三种方式都有效
auth认证模块
Django2.X以上不同于django1.X,先登录django自带admin后台管理页面需要先执行数据库迁移命令
python3.8 manage.py migrate
# 执行完,就可以在浏览器访问该后台页面
执行完,会产生一个auth_user表
author_user表的功能
- 该表可以配合auth模块做用户相关的功能:注册、登录、修改密码、注销...
- 该表还是django的admin后台管理默认的表
admin后台管理员账号创建
python3.8 manage.py createsuperuser
创建好超级管理员账号后就可以登录admin(后台只有超级管理员才能登录)
校验用户名和密码是否正确
# 视图函数
from django.contrib import auth
def login(request):
if request.method=='POST':
username=request.POST.get('username')
password=request.POST.get('password')
is_login_obj=auth.authenticate(username=username,password=password) # 校验成功返回对象,没有返回None
print(is_login_obj) # 一旦登录成功,浏览器发送请求后,会打印出当前登录对象名字
return render(request,'login.html')
# 模版层
<form action="" method="post">
{% csrf_token %}
<p>username:<input type="text" name="username"></p>
<p>password:<input type="text" name="password"></p>
<p><input type="submit" value="登录"></p>
</form>
创建用户
错误演示
from django.contrib.auth.models import User
def add_user(request):
User.objects.create(username='jason',password='123')
return HttpResponse('添加用户')
用orm的语法创建auth_user表中的用户名及密码:password没有加密
我们应该用auth模块给我们提供的方法
def add_user(request):
User.objects.create_user(username='jason1',password='123') # 创建普通用户,无法登录admin管理页面
# User.objects.create_superuser() # 创建超级管理员用户(需要加一个email字段)
return HttpResponse('用户')
用户登录
from django.contrib import auth
auth.login(request,login_obj)
该方法会自动在服务端的django_session表中添加session键值对,并发送给浏览器一个sessionid表示登录状态
判断用户是否登录
request.user.is_authenticated # False 与True
获取登录对象
request.user
如果页面有sessionid,返回登录用户名,如果没有sessionid,则返回AnonymousUser
校验用户登录装饰器
第一种:在装饰器加括号指定未登录后跳转路径,不指定就默认挑战到accounts/login
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')
def add_user(request):
return HttpResponse('add_user')
第二种:在settings配置文件中添加指定跳转路径
# settings.py
LOGIN_URL='login'
校验密码是否正确
request.user.check_password(old_password) # 返回True False
修改密码
request.user.set_password(new_password)
request.user.save()
注销登录
auth.logout(request)
auth_user表切换
当我们需要的字段不在auth_user表的字段中,可以自己写个表切换auth_user表,分为如下几步:
- 导入模块,继承auth_user模型类
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
phone=models.BigIntegerField()
- 配置文件中注册配置
AUTH_USER_MODEL='app01.UserInfo' # 不要app01中间不要加models,会报错
- 执行数据库同步迁移操作
基于django中间件设计项目功能
基于django中间件思想的功能插拔式设计
improtlib模块:通过字符串导入模块,最小单位是py文件
├── app01
│ ├── rcb
│ │ ├── __init__.py # 包init文件
│ │ ├── msg.py
│ │ ├── qq.py
│ │ └── wx.py
│ ├── start.py # 启动文件
│ ├── settings.py # 配置文件
rcb下面的msg,qq,wx文件都是不同的通讯方式,代码为:
class Msg(object):
def __init__(self):
pass
def send(self,content):
print(self.__class__.__name__.lower(),'发送消息:',content)
class Qq(object):
def __init__(self):
pass
def send(self,content):
print(self.__class__.__name__.lower(),'发送消息:',content)
class Wx(object):
def __init__(self):
pass
def send(self,content):
print(self.__class__.__name__.lower(),'发送消息:',content)
配置文件
RCB=['app01.rcb.msg.Msg',
'app01.rcb.qq.Qq',
'app01.rcb.wx.Wx'
]
rcb的__init__
文件
import importlib
from djangoProject3 import settings
def send_all(content):
for i in settings.RCB:
module_path,class_str_name=i.rsplit('.',maxsplit=1)
module_obj=importlib.import_module(module_path)
class_name=getattr(module_obj,class_str_name)
obj=class_name()
obj.send(content)
start.py
from app01 import rcb
if __name__ == '__main__':
rcb.send_all("你们好")