【2022-09-13】Django框架(十)

Django框架(十)

CSRF跨站请求伪造

CSRF(Cross Site Request Forgery) 跨站请求伪造。也被称为One Click Attack和Session Riding,通常缩写为CSRF或XSRF。如果从名字你还不不知道它表示什么,你可以这样理解:攻击者(黑客,钓鱼网站)盗用了你的身份,以你的名义发送恶意请求,这些请求包括发送邮件、发送消息、盗取账号、购买商品、银行转账,从而使你的个人隐私泄露和财产损失。

模拟CSRF攻击实例

  • 光听概念可能大家对于CSRF还是不够了解,下面我将举一个例子来让大家对CSRF有一个更深层次的理解。
# 以钓鱼网站为例:
	我搭建一个跟正规网站一模一样的界面(中国银行)
	用户不小心进入到了我们的网站,用户给某个人打钱
	打钱的操作确确实实是提交给了中国银行的系统,用户的钱也确确实实减少了
	但是唯一不同的时候打钱的账户不是用户想要打的账户变成了一个莫名其妙的匿名账户
# 内部本质
	我们在钓鱼网站的页面 针对对方账户 只给用户提供一个没有name属性的普通input框
	然后我们在内部隐藏一个已经写好name和value的input框
    那么我们在提交的时候只向后端提交存在name属性和value值得input框内容
    此时隐藏得input框得name和value属性值是固定得,相当于'诈骗'账户

示例:

真实网站端口号8000:html

<h1>我是真正的网站</h1>
<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>
    <p><input type="submit"></p>
 
</form>

钓鱼网站端口号8001:html

<h1>我是钓鱼网站</h1>
<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 type="text" name="target_user" value="gary" style="display: none">
    <p>money: <input type="text" name="money"></p>
    <p><input type="submit"></p>
</form>
 
需要将钓鱼网站的端口号修改,不然冲突

display:none
它可以隐藏某个元素,且隐藏的元素不会占用任何空间。也就是说,该元素不但被隐藏了,而且该元素原本占用的空间也会从页面布局中消失。

那么,上述的问题应该如何避免呢?

# csrf跨站请求伪造校验
		网站在给用户返回一个具有提交数据功能页面的时候会给这个页面加一个唯一标识
		当这个页面朝后端发送post请求的时候 我的后端会先校验唯一标识,如果唯一标识不对直接拒绝(403 forbbiden)如果成功则正常执行

form如何伪造校验

前提:

'django.middleware.csrf.CsrfViewMiddleware',  # 需要去配置文件将csrf中间件打开
# 只需要在form表单内添加:{% csrf_token %}
 
<h1>我是真正的网站</h1>
<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>
    <p><input type="submit"></p>
</form>

Ajax如何伪造校验

# 除了form表单可以提交post请求之外,ajax也是可以提交post请求的
# 那么上述方法只是针对form表单的,那么我们接下来来研究ajax是如何

方式一:

// 第一种 利用标签查找获取页面上的随机字符串
<script>
    $('#d1').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':$('[name=username]').val(),'target_user':$('[name=target_user]').val(),'money':$('[name=money]').val(),'csrfmiddlewaretoken':$('[name=csrfmiddlewaretoken]').val()},
            // 主动取出csrfmiddlewaretoken对应的值发送给后端
            success:function (){
            }
        })
    })
</script>

方式二:

# 利用模版语法提供的快捷书写 (如果是前后端分离的项目,就不方便使用该方法)
    
data:{'username':$('[name=username]').val(),'target_user':$('[name=target_user]').val(),'money':$('[name=money]').val(),'csrfmiddlewaretoken':{{ csrf_token }},

方式三:

第三种 通用方式直接拷贝js代码并应用到自己的html页面上即可 

{% load static  %}
<script src="{% static 'js/mycsrf.js' %}"></script>
 
 
data:{'username':$('[name=username]').val(),'target_user':$('[name=target_user]').val(),'money':$('[name=money]').val()},

配置文件:

# 需手动创建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);
    }
  }
});

CSRF相关装饰器

# 需求:
    1.网站整体都不校验csrf,就单单几个视图函数校验
    2.网站整体都校验csrf,就单单几个不视图函数不校验
    
# 这里django给我们提供了现成的两个装饰器供我们使用
    1. csrf_protect   需要校验
    2. csrf_exempt   忽视校验	

csrf_protect需要校验

csrf_exempt忽视校验

CBV添加装饰器的三种方式

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')

总结

针对csrf_protect:需要校验
	三种方式都可行。
针对csrf_exempt:忽视校验
	只有给dispatch方法添加才可行
    (可以在dispatch方法紧上方添加装饰器,也可以使用@method_decorator(csrf_exempt,name='dispatch')在类上方添加装饰器)

用户认证Auth模块

什么是Auth模块

Auth模块是Django自带的用户认证模块:
	我们在开发一个网站的时候,无可避免的需要设计实现网站的用户系统。此时我们需要实现包括用户注册、用户登录、用户认证、注销、修改密码等功能,这些功能几乎是所有软件都需要开发的功能,那么我们之前在写这些功能的时候也是需要花费一些时间书写代码逻辑的,那么Django自带的auth模块就会很轻松的帮助我们完成这些功能,它默认使用 auth_user 表来存储用户数据。

auth_user表

# 其实我们很早就应该见过这张表只是把它忽略了。
	- 其实我们在创建好一个django项目之后直接执行数据库迁移命令会自动生成很多表,那么其中一张表就是auth_user表
 
# 基本用处:
    - django在启动之后就可以直接访问admin路由,需要输入用户名和密码,数据参考的就是auth_user表,并且还必须是管理员用户才能进入
    
# 怎么创建超级用户(管理员)
	- 首先要数据库迁移
        makemigrations
        migrate
    - 创建超级用户命令:	
        python3 manage.py createsuperuser

auth_user表实现用户注册、登录、注销....功能

登录功能:

# 校验用户民或密码是否一致
# 关键字:auth.authenticate(username=用户输入的用户名,password=用户输入的密码)
# 需导入模块:
from django.contrib import auth
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 那么现在如何校验数据呢?
            # 获取表的数据 但是密码是密文的怎么比对呢
        # 这时就要用到auth模块
        res = auth.authenticate(request,username=username,password=password)
        '''
            他都干了那些事:
                自动查找auth_user表
                自动给密码加密后再进行比对
            注意事项:
                括号内必须同时传入用户名和密码
                不能只传用户名
        '''
        print(res,type(res))
        print(res.username)
        print(res.password)
    return render(request,'login.html')

保存用户的登录状态

# 关键字:auth_login(request,用户对象)

# 方法优点:只要执行了auth.login方法,就可以在任何地方通过request.user获取到当前登录的用户对象
 
 
 
def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
 
        user_obj = auth.authenticate(request,username=username,password=password)
        if user_obj:
            # 之前我们保存用户登录状态是不是要通过session来保存。现在可通过auth来直接帮助我们操作session表
            # 保存用户登录状态:
            auth.login(request,user_obj)  # 类似于request_session[key] = user_obj
            return redirect('/home/')   # 跳转到home页面
 
 
    return render(request,'login.html')
 
def home(request):
    print(request.user)   # 可以拿到当前登录的用户对象
    return HttpResponse('home')

判断用户是否登录

# 如上:那么如果没有登录,直接访问home页面,requeest.user返回的是什么呢?

# 结果:如果没有登录则返回的是AnonymousUser匿名用户
 
# 那么如果判断用户是否登录了呢?
# 关键字:request.user.is_authenticated
# 返回结果:布尔类型:
        - 未登录(匿名用户)	 :返回False
        - 已登录			:返回True

校验登录装饰器

# 上述看到我们匿名用户也可以登录到home页面,这样是不合理的,需要登陆后才能访问其他页面才合理。那么就需要校验用户是否登录
 
# 需导入模块:
    - from django.contrib.auth.decorators import login_required
# 关键字:添加装饰器
    - 局部配置:@login_required(login_url='/指定未登录跳转页面/')
    - 全局配置:@login_required   # 无需指定跳转路径
    	# 需要在settings.py配置文件中配置:LOGIN_URL = '/指定未登录跳转页面/'

局部和全局哪个好呢?
    全局的好处在于无需重复写代码 但是跳转的页面却很单一
    局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面

修改密码

# 修改密码:
    - 需要校验旧密码是否正确
    - 需要将新密码从auth_user表中修改并保存
<form action="" method="post">
    {% csrf_token %}
    <p>username<input type="text" name="username" disabled value="{{ request.user.username }}"></p>
    <p>old_password:<input type="text" name="old_password"></p>
    <p>new_password:<input type="text" name="new_password"></p>
    <p>confirm_password:<input type="text" name="confirm_password"></p>
    <input type="submit">
    
</form>
# 校验旧密码:
# 关键字:request.user.check_password('需要校验的数据(旧密码)')
     	- 相当于:将当前表的password字段和用户输入的旧密码进行比对
    
# 修改密码并同步到数据库:
# 关键字:request.user.set_password(此处放新密码)
@login_required
def set_password(request):
    if request.method == 'POST':
        old_password = request.POST.get('old_password')
        new_password = request.POST.get('new_password')
        confirm_password = request.POST.get('confirm_password')
        # 校验两次密码是否一直
        if new_password == confirm_password:
            # 校验老密码:
            is_right =  request.user.check_password(old_password)   # 返回的是布尔值
            if is_right:
                # 修改密码
                request.user.set_password(new_password)  # 修改对象的属性
                request.user.save()   # 同步到数据库
        return redirect('/login/')   # 注册完之后跳转到登录页面
    return render(request,'setpassword.html',locals())

注销功能

# 注销则是将session表中的用户数据删除即可。
# 关键字:auth.logout(request)
 
 
@login_required   # 登录才能注销所以也需要验证是否登录。
def logout(request):
    auth.logout(request)
    return redirect('/login/')

注册功能

# 原理:操作auth_user表写入数据:
# 关键字:User.objects.create(username=username,password=password)
 
# 需导入模块:
from django.contrib.auth.models import User
 
def register(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 操作auth_user表写入数据
        User.objects.create(username=username,password=password)
 
    return render(request,'register.html')

# 综上:写入数据 不能用create 密码没有加密 处理
 
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)

总结:

# 1.比对用户名和密码是否正确
user_obj = auth.authenticate(request,username=username,password=password)
# 括号内必须同时传入用户名和密码
print(user_obj)  # 返回用户对象   数据不符合则返回None
print(user_obj.username)  # 用户名
print(user_obj.password)  # 密文
 
# 2.保存用户状态
auth.login(request,user_obj)  # 类似于request.session[key] = user_obj
# 只要执行了该方法 你就可以在任何地方通过request.user获取到当前登陆的用户对象
 
# 3.判断当前用户是否登陆
request.user.is_authenticated()
 
# 4.获取当前登陆用户对象
request.user
 
# 5.校验用户是否登陆装饰器
from django.contrib.auth.decorators import login_required
# 局部配置
@login_required(login_url='/login/') 
# 全局配置
LOGIN_URL = '/login/'
	1.如果局部和全局都有 该听谁的?
    局部 > 全局
	2.局部和全局哪个好呢?
    全局的好处在于无需重复写代码 但是跳转的页面却很单一
    局部的好处在于不同的视图函数在用户没有登陆的情况下可以跳转到不同的页面
 
# 6.比对原密码
request.user.check_password(old_password)
 
# 7.修改密码
request.user.set_password(new_password)  # 仅仅是在修改对象的属性
request.user.save()  # 这一步才是真正的操作数据库
 
# 8.注销
auth.logout(request) 
 
# 9.注册
# 操作auth_user表写入数据
User.objects.create(username=username,password=password)  # 写入数据  不能用create 密码没有加密处理
# 创建普通用户
User.objects.create_user(username=username,password=password)
# 创建超级用户(了解):使用代码创建超级用户 邮箱是必填的 而用命令创建则可以不填
User.objects.create_superuser(username=username,email='123@qq.com',password=password)

如何扩展auth_user表

# 比如给auth_user表添加一个phone字段,那么该如何扩建呢?
 
from django.contrib.auth.models import User   # auth_user所在的表位置
 
我们先来看一下这张表都有那些字段:

# 所以我们在扩展auth_user表是只需要继承AbstractUser即可
# 举例:
# 需导入模块:
from django.contrib.auth.models import User,AbstractUser
 
class UserInfo(AbstractUser):
    phone = models.BigIntegerField()
 
# 需要注意:
	如果继承了AbstractUser
    那么在执行数据库迁移命令的时候auth_user表就不会再创建出来了
    而UserInfo表中会出现auth_user所有的字段外加自己扩展的字段
    这么做的好处在于我们能够直接点击我们自己的表更加快捷的完成操作以及扩展
    
    - 前提:
    	1、在继承之前没有执行过数据库迁移的命令
        	即auth_user表没有被创建出来,如果当前库已经创建了那么就需要重新创建一个库
        2、继承的类里面不要覆盖AbstractUser里面的字段名
        	表里面有的字段不要动,只扩展额外的字段即可
        3、需要再配置文件中告诉django你要用UserInfo代替auth_user
        	AUTH_USER_MODEL = 'app01.UserInfo'   # 应用名.表名

如果自己写表替代了auth_user那么
auth模块的功能还是照常使用,参考的表由原来的auth_user变成了UserInfo
# 比如:
	- 原先:
        from django.contrib.auth.models import User
        User.objects.create(username=username,password=password)
        
    - 现在:
        from app01 import models
        models.UserInfo.objects.create(username=username,password=password)
        
# 方法不变只是操作的表换成了自己指定的表。

字符串导入importlib模块

# 模块:importlib
import importlib
res = 'myfile.b'
ret = importlib.import_module(res)  # 相当于:from myfile import b
# 该方法最小只能到py文件名
print(ret)
 
from myfile import b
print(b)

importlib应用场景

# 需求:发送通知的功能,发微信、qq、邮箱,同时通过这三个功能给客户发消息,可以随时禁止某一个板块的通知(只给微信qq发送通知,不给邮箱发)

普通版本

功能文件:notify.py

# 简单模拟三个功能
 
def wechat(content):
    print('微信通知:%s'%content)
 
def qq(content):
    print('qq通知:%s'%content)
 
def email(content):
    print('email通知:%s'%content)

启动文件:start.py

from notify import *   # 导入所有的函数
 
def send_all(content):   # 定义一个函数执行所有的功能函数
    wechat(content)
    qq(content)
    email(content)
 
if __name__ == '__main__':
    send_all('你的特别关心给你发消息了')   # 执行执行函数触发所有的功能函数

进阶版本

创建一个notify文件夹,下面存储的都是功能模块,那么这就相当于一个包,包就需要有一个__init__.py文件,如果想要找包下面的所有模块的话,就直接通过__init__.py文件即可。

setting.py

NOTIFY_LIST = [
    'notify.email.Email',   # 存储各功能模块的路径
    'notify.qq.QQ',
    'notify.wechat.Wechat',
]

notify文件夹下:wechat.py

class Wechat(object):
    def __int__(self):
        pass   # 发送微信需要做的前期准备
 
    def send(self,content):
        print('微信通知:%s'%content)

notify文件夹下:qq.py

class QQ(object):
    def __int__(self):
        pass # 发送qq需要做的前期准备
 
    def send(self, content):
        print('QQ通知:%s' % content)

notify文件夹下:email.py

class Email(object):
    def __int__(self):
        pass # 发送email需要做的前期准备
 
    def send(self, content):
        print('邮箱通知:%s' % content)

notify文件夹下:init.py

import settings
import importlib
 
# 定义一个send_all方法
def send_all(content):
    for path_str in settings.NOTIFY_LIST:  # 这样就拿到的是settings下的列表:'notify.email.Email',
        module_path, class_name = path_str.rsplit('.',maxsplit=1)  # 使用字符串右切割:以 . 来切割切一位
        # 这样解压复制:module_path = notify.email   class_name=Email
 
        # 利用字符串的导入:notify.email
        module = importlib.import_module(module_path)  # 返回的是一个对象
        # 这样就相当于:from notify import email
 
        # 运用反射:拿到module对象里的class_name(Email)类
        cls = getattr(module,class_name)
 
        # 生成类的对象
        obj = cls()
 
        # 利用鸭子类型直接调用send方法
        obj.send(content)

启动文件start.py

import notify
 
notify.send_all('特别关心下线了')

posted @ 2022-09-13 21:38  dy12138  阅读(62)  评论(0)    收藏  举报