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

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

一、csrf跨站请求伪造(Cross—Site Request Forgery)

1.简介

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

2、模拟

一台计算机上两个服务端不同端口号启动,钓鱼网站提交地址改为正规网站的地址。
######真正网站######
真正网站views.py:
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(f'{username}给{target_user}转账了{money}元')
    return render(request, 'transfer.html')

真正网站html:
<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>
    <input type="submit">
</form>

######钓鱼网站######
钓鱼网站views.py:
def transfer(request):
    return render(request, 'transfer.html')

钓鱼网站html:
<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">
        <input type="text" name="target_user" value="假小橘" style="display: none">
    </p>
    <p>money:
        <input type="text" name="money">
    </p>
    <input type="submit">
</form>

3、预防

crsf策略:通过在返回页面上添加独一无二的标识信息从而区分正规网站和钓鱼网站的请求。

MIDDLEWARE = [
    'django.middleware.csrf.CsrfViewMiddleware',
]

二、csrf操作

1、form表单

# 前后端不分离
<form action="" method="post">
    {% csrf_token %}
</form>

html:
<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、Ajax

方式一:先编写csrf模板语法,然后利用标签查找和值获取,手动添加

{% csrf_token %}
data:{'username':'jason', 'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},

html:
<h1>这是真正的网站</h1>
{% csrf_token %}
<button id="d1">点我发送Ajax请求</button>
<script src="/static/csrf.js"></script>
<script>
    $('#d1').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'jason', 'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()},
            success:function (args){

            }
        })
    })
</script>

方式二:直接利用模板语法即可

html:
<h1>这是真正的网站</h1>
{% csrf_token %}
<button id="d1">点我发送Ajax请求</button>
<script src="/static/csrf.js"></script>
<script>
    $('#d1').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'jason', 'csrfmiddlewaretoken':'{{ csrf_token }}'},
            success:function (args){

            }
        })
    })
</script>

方式三:通用方式(js脚本)

# 前后端分离
data:{'username':'jason'},

html:
<h1>这是真正的网站</h1>
<button id="d1">点我发送Ajax请求</button>
<script src="/static/csrf.js"></script>
<script>
    $('#d1').click(function (){
        $.ajax({
            url:'',
            type:'post',
            data:{'username':'jason'},
            success:function (args){

            }
        })
    })
</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);
    }
  }
});

三、csrf相关装饰器

当整个网站默认都不校验csrf但是局部视图函数需要校验,如何处理?

当整个网站默认都校验crsf但是局部视图函数不需要校验,如何处理?

1、FBV

from django.views.decorators.csrf import csrf_protect, csrf_exempt
"""
csrf_protect 校验csrf
csrf_exempt 不校验csrf
"""
@csrf_protect
def home(request):
    return HttpResponse('哼,这个教室没有以前教室好。呜呜呜~')

2、CBV

针对CBV不能直接在方法上添加装饰器,需要借助专门添加装饰器的方法

csrf_protect:

方式一:直接在类中的某个方法上添加,指名道姓的添加

from django import views
from django.utils.decorators import method_decorator

class MyHome(views.View):
    def get(self, request):
        return HttpResponse('Home GET view')
    @method_decorator(csrf_protect)
    def post(self, request):
        return HttpResponse('Home POST view')

方式二:直接在类名上添加并指定参数,指名道姓的添加

@method_decorator(csrf_protect, name='post')  # 方式二
class MyHome(views.View):
    def get(self, request):
        return HttpResponse('Home GET view')

    def post(self, request):
        return HttpResponse('Home POST view')

方式三:重写dispath方法并添加作用于类中所有的方法,影响类中所有的方法

class MyHome(views.View):
    @method_decorator(csrf_protect)  # 影响类中所有的方法
    def dispatch(self, request, *args, **kwargs):  # 路由匹配成功之后执行dispatch方法
        super(MyHome,self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('Home GET view')

    def post(self, request):
        return HttpResponse('Home POST view')
csrf_exempt:

方式一和方式二没有效果

class MyHome(views.View):
    def dispatch(self, request, *args, **kwargs):  # 路由匹配成功之后执行dispatch方法
        super(MyHome,self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('Home GET view')

    @method_decorator(csrf_exempt)  # 无效
    def post(self, request):
        return HttpResponse('Home POST view')
    
    
@method_decorator(csrf_exempt, name='post')  # 方式二
class MyHome(views.View):
    def dispatch(self, request, *args, **kwargs):  # 路由匹配成功之后执行dispatch方法
        super(MyHome,self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('Home GET view')

    def post(self, request):
        return HttpResponse('Home POST view')

针对方式三有效:影响类中所有方法

class MyHome(views.View):
    @method_decorator(csrf_exempt)  # 影响类中所有的方法
    def dispatch(self, request, *args, **kwargs):  # 路由匹配成功之后执行dispatch方法
        return super(MyHome,self).dispatch(request, *args, **kwargs)

    def get(self, request):
        return HttpResponse('Home GET view')

    def post(self, request):
        return HttpResponse('Home POST view')

总结:针对csrf_exempt只有方式三有效,针对其它装饰器上述三种方式都有效。

四、auth认证模块

django执行数据库迁移命令之后会产生auth_user表

该表可以配合auth模块做用户相关的功能:注册、登录、修改密码、注销...

该表还是django admin后台管理默认的表,django admin后台管理员账号创建:python manage.py createsuperuser

auth模块常见功能

1.创建用户

from django.contrib.auth.models import User
User.object.create_user(username,password)
User.object.create_superuser(username,password,email)

######代码展示######
from django.contrib.auth.models import User
def register(request):
    # User.objects.create(username='jason',password=123)  # 不能使用,因为密码并没有加密
    # User.objects.create_user(username='jasonNB',password=123)  # 普通用户,不能进入后台管理
    User.objects.create_superuser(username='jason666', password=123, email='123@qq.com')  # 超级管理员必须要有邮箱字段,否则会报错

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

from django.contrib import auth
auth.authenticate(request,username,password)
authenticate:数据正确的情况下返回的是数据对象;数据错误的情况下返回的是None。

######代码展示######
from django.contrib import auth

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 查看数据库校验数据
        is_user_obj = auth.authenticate(request, username=username, password=password)
        if is_user_obj:
            auth.login(request, is_user_obj)  # 自动操作cookie和session
    return render(request, 'login.html')

3.用户登录

auth.login(request,user_obj)

######代码展示######
from django.contrib import auth

def login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 查看数据库校验数据
        is_user_obj = auth.authenticate(request, username=username, password=password)
        '''
        数据正确的情况下返回的是数据对象
        数据错误的情况下返回的是None
        '''
        # print(res.username)  # admin
        # print(res.password)  # 密文
        # print(res.pk)  # 1
        if is_user_obj:
            auth.login(request, is_user_obj)  # 自动操作cookie和session
    return render(request, 'login.html')

4.判断用户是否登录

request.user.is_authecticated  # 判断当前用户是否登录

######代码展示######
from django.contrib import auth

def login(request):
    print(request.user.is_authenticated)  # 判断当前用户是否登录
    return render(request, 'login.html')

5.获取登录用户对象

request.user
当用户登录成功之后(执行了auth.login),该方法返回当前登录用户对象;
当用户没有登录成功(没有执行auth.login),该方法返回匿名用户对象。


######代码展示######
from django.contrib import auth

def login(request):
    print(request.user)
    """
    当用户登录成功之后(执行了auth.login),该方法返回当前登录用户对象
    当用户没有登录成功(没有执行auth.login),该方法返回匿名用户对象
    """
    print(request.user.is_authenticated)  # 判断当前用户是否登录
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        # 查看数据库校验数据
        res = auth.authenticate(request, username=username, password=password)
        '''
        数据正确的情况下返回的是数据对象
        数据错误的情况下返回的是None
        '''
        print(res.username)  # admin
        print(res.password)  # 密文
        print(res.pk)  # 1
    return render(request, 'login.html')

6.校验用户登录装饰器

from django.contrib.auth.decorators import login_required
跳转局部配置
@login_required(login_url='/login/')   # 装饰函数

跳转全局配置
@login_required  # 装饰函数
LOGIN_URL = '/login/'  # settings.py里面配置

######代码展示######
# 局部配置
from django.contrib.auth.decorators import login_required
@login_required(login_url='/login/')  # 校验用户是否登录,默认跳转的登录地址比较复杂,我们可以自定义跳转的登录地址
def index(request):
    return HttpResponse('index view')

@login_required(login_url='/login/')  # 局部配置每次都需要自己写,量大的情况下不方便
def func(request):
    return HttpResponse('func view')


# 全局配
@login_required  # 校验用户是否登录,默认跳转的登录地址比较复杂,我们可以自定义跳转的登录地址
def index(request):
    return HttpResponse('index view')

@login_required  # 局部配置每次都需要自己写,量大的情况下不方便
def func(request):
    return HttpResponse('func view')

LOGIN_URL = '/login/'  # settings.py里面配置

7.校验密码是否正确

request.user.check_password(old_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('firm_password')
        # 校验原密码是否正确
        is_right = request.user.check_password(old_password)  # 自动加密并校验
    return render(request, 'set_password.html')

8.修改密码

request.user.set_password(new_passowrd)
request.user.save()

######代码展示######
@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('firm_password')
        # 校验原密码是否正确
        is_right = request.user.check_password(old_password)  # 自动加密并校验
        if is_right:
            # 修改密码
            request.user.set_password(new_password)
            # 保存数据
            request.user.save()
    return render(request, 'set_password.html')

9.注销登录

auth.logout(request)

######代码展示######
@login_required
def logout(request):
    auth.logout(request)  # 自动清除cooki和session
    return HttpResponse('注销成功')

五、auth_user表切换

1.models.py
from django.contrib.auth.models import AbstractUser

class Userinfo(AbstractUser):  # 想自定需要在扩张auth_user表中没有的字段
    """扩展auth_user表中没有的字段"""
    # 若已经执行数据库迁移命令那么需要换新的数据库
    phone = models.BigIntegerField(null=True)
    desc = models.TextField(null=True)

2.settings.py
# 切换auth_user表配置
AUTH_USER_MODEL = 'app01.Userinfo'


'''
注意:
	自定义user表需要继承AbstractUser之外还需要扩展auth_user表中没有的字段
	若已经执行数据库迁移命令需要更换新的数据库(就是没有执行过数据库迁移命令)
'''

六、基于django中间件设计项目功能

模块的导入:
	import 句式
	from ... import ... 句式
    
1.简单的函数封装
def send_qq(content):
    print('qq消息通知:', content)


def send_wx(content):
    print('vx消息通知:', content)


def send_msg(content):
    print('msg消息通知:', content)


def send_all(content):
    send_qq(content)
    send_wx(content)
    send_msg(content)

if __name__ == '__main__':
    send_all('今天换座位了,我不能回头就能看见')
    
    
2.配置文件插拔式设计
### qq.py
class QQ(object):
    def __init__(self):
        pass  # 模拟发送QQ之前,应该准备的代码环境

    def send(self, content):
        print('qq消息通知:', content)
        
### vx.py
class Vx(object):
    def __init__(self):
        pass  # 模拟发送Vx之前,应该准备的代码环境

    def send(self, content):
        print('vx消息通知:', content)

### settings.py
NOTIFY_FUNC_LIST = [
    'test.notify.qq.QQ',
    'test.notify.vx.Vx',
    'test.notify.msg.Msg',
]

### __init__.py (核心代码)
import importlib
import settings
def send_all(content):
    for i in settings.NOTIFY_FUNC_LIST:  # 'day913_django.notify.qq.QQ'
        module_path, class_str_name = i.rsplit('.', maxsplit=1)  # 'day913_django.notify.qq' 'QQ'
        module = importlib.import_module(module_path)  # from day913_django.notify import qq, vx, msg
        class_name = getattr(module, class_str_name)  # 真正的类名
        obj = class_name()
        obj.send(content)
        
### start.py
from test import notify
if __name__ == '__main__':
    notify.send_all('好好学习,天天向上')
posted @ 2022-09-13 21:22  努力努力再努力~W  阅读(24)  评论(0)    收藏  举报