csrf跨站请求伪造、csrf相关装饰器、auth认证模块、auth_user表切换以及基于django中间件设计项目功能

csrf跨站请求伪造

什么是跨站请求伪造

假的网站模拟真网站页面通过提交请求路径到真网站服务器,给服务器虚假信息

image-20220913144457469

模拟钓鱼网站

假设是一个跟银行一模一样的网址页面,用户在该页面上转账,账户的钱会减少,但是受益人却不是自己想要转账的那个人。

可以用两个服务器端口分别充当正规网站服务器以及假网站服务器,并启动,然后将钓鱼网站的提交地址改成正规网站的地址。

假网站页面

<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的随机字符串)从而区分正规网站和钓鱼网站的请求,就可以阻止跨站请求伪造。

操作流程

  1. django自带csrf的中间件,需要确保不被注释掉
MIDDLEWARE = [
    'django.middleware.csrf.CsrfViewMiddleware',
]
  1. 需要在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>
  1. 查看该form表单的前端页面,看{% csrf_token %}模版语法的前端具体展示
image-20220913150344126

我们由图可以看出,{% csrf_token %}其实就是一个input框,并且这个input框的value每当页面刷新一次都会更新,数据提交后,后端会收到带有csrf随机字符串的值.

  1. 后端获取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装饰器,有如下三种方法

  1. 指名道姓的添加,在请求方法上,影响的是当前添加的方法
class Index(views.View):
    def get(self,request):
        return HttpResponse('get请求')
    @method_decorator(csrf_protect)  ----> 
    def post(self,request):
        return HttpResponse('post请求')
  1. 在类上添加,指名道姓的添加,用name='xxx'指定,影响的是name指定的方法
@method_decorator(csrf_protect,name='post')
class Index(views.View):
  1. 重写父类中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表

image-20220913175945572

author_user表的功能

  • 该表可以配合auth模块做用户相关的功能:注册、登录、修改密码、注销...
  • 该表还是django的admin后台管理默认的表

admin后台管理员账号创建

python3.8 manage.py createsuperuser

创建好超级管理员账号后就可以登录admin(后台只有超级管理员才能登录)

image-20220913180527298

校验用户名和密码是否正确

# 视图函数
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没有加密

image-20220913190618649

我们应该用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表,分为如下几步:

  1. 导入模块,继承auth_user模型类
from django.contrib.auth.models import AbstractUser
class UserInfo(AbstractUser):
    phone=models.BigIntegerField()
  1. 配置文件中注册配置
AUTH_USER_MODEL='app01.UserInfo'  # 不要app01中间不要加models,会报错
  1. 执行数据库同步迁移操作

基于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("你们好")
image-20220913232834406
posted @ 2022-09-13 23:34  荀飞  阅读(22)  评论(0编辑  收藏  举报